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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ This project follows a Keep a Changelog-style format and uses version tags for r

## [Unreleased]

## [1.6.8] - 2026-08-09

### Changed
- **Cutting a release REQUIRES a GitHub Release (`gh release create`), not just
a git tag.** `scripts/tag_release.sh` now creates both — it pushes the tag and
then creates the GitHub Release with the notes from the matching
`CHANGELOG.md` section (falling back to generated notes if the section is
missing). A bare `git tag` + push never surfaces on `/releases` or as `Latest`,
so a hand-tag-only release cut is now an explicit rule violation — codified in
`CLAUDE.md` → Release Process, with a manual-recovery playbook for when the
`gh release create` step fails.
- **% readouts and the fan spinner tint by utilization color.** The headline %
in each metric row (and the fan-glyph) now wears the same severity color as
the sparkline that traces it — a 90% reading reads red, an idle 5% stays cool.
Expand Down
9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ This file is the single source of truth for repository guidelines, used by Claud

`main` is **PR-only** (branch protection + `.githooks/pre-commit` redaction check and `.githooks/pre-push` guard; run `git config core.hooksPath .githooks` once). The Homebrew formula lives in the separate tap repo `binlecode/homebrew-actop` (not this repo); CI syncs it on tag and publishes to PyPI via OIDC. CI/CD mechanics are owned by the workflow files themselves (`.github/workflows/main-ci.yml`, `release-formula.yml`, `publish-pypi.yml`) — each carries its one-time setup inline.

**RULE — cutting a release REQUIRES a GitHub Release, not just a tag.** A release is cut with `scripts/tag_release.sh`, which pushes the git tag **and** creates the GitHub Release for it via `gh release create` (release notes pulled from the matching `CHANGELOG.md` section). A bare `git tag` + push is **not** a release: it never surfaces on the `/releases` page or as `Latest`, and leaves the Releases history silently behind the code. Never cut a release by hand-tagging alone — if the `gh release create` step fails, finish it manually (see playbooks below) before calling the release cut done.

### Release steps

```bash
Expand All @@ -68,7 +70,7 @@ gh pr create --base main --fill
gh pr merge --merge --delete-branch
git checkout main && git pull --ff-only

# 4. Tag the release (after the bump PR merges)
# 4. Cut the release — tag + GitHub Release (RULE: tag alone is not a release)
scripts/tag_release.sh "X.Y.Z"

# 5. Monitor CI — wait for release-formula and publish-pypi to pass
Expand All @@ -86,6 +88,11 @@ brew update && brew upgrade binlecode/actop/actop # Homebrew (tap repo synced
git fetch origin && git checkout main && git pull --ff-only origin main
scripts/tag_release.sh "X.Y.Z"
```
- **`gh release create` fails or is skipped (auth/permissions):** the tag may be pushed but **no GitHub Release exists** — the release cut is not done until it does. Confirm `gh auth status` and re-run just the release step against the existing tag:
```bash
gh release create "vX.Y.Z" --title "actop X.Y.Z" --notes "$(awk '/^## \['"X.Y.Z"'\]/ {f=1; next} f && /^## \[/ {exit} f' CHANGELOG.md)"
```
Never leave a release cut as a bare tag — verify it appears on `/releases` before moving on.
- **`release-formula` fails with tag/version mismatch:** tag `vX.Y.Z` doesn't match `pyproject.toml` in the tag commit. Fix in a new PR, merge, then create a **new** tag (never reuse the old tag).
- **`release-formula` fails at tap checkout/push:** missing or under-scoped `HOMEBREW_TAP_TOKEN`. Confirm the secret exists on `binlecode/actop` and the PAT has **Contents: Read/write** on `binlecode/homebrew-actop`. Re-run the failed job.
- **`publish-pypi` fails with OIDC / trusted-publisher error:** verify the trusted publisher on pypi.org matches repo `binlecode/actop`, workflow `publish-pypi.yml`, environment `pypi`; confirm the run is from a `v*` tag (the `pypi` environment's deploy policy is tag-only). `skip-existing` makes re-runs safe. **Break-glass:** publish that version manually with the token-driven Flow B (`twine upload --skip-existing`), then restore OIDC for subsequent releases.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "actop"
version = "1.6.7"
version = "1.6.8"
description = "Apple Silicon (M1–M4) power, GPU, ANE & memory-bandwidth monitor — sudoless TUI + Python API for profiling local LLM / MLX / CoreML inference"
readme = "README.md"
license = { text = "MIT" }
Expand Down
30 changes: 29 additions & 1 deletion scripts/tag_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Behavior:
- Verifies tag does not already exist locally/remotely
- Verifies local main is in sync with origin/main (no-op push)
- Creates and pushes tag vX.Y.Z
- Creates the GitHub Release for vX.Y.Z (gh release create) with the notes
from the matching CHANGELOG.md section

RULE: a release cut REQUIRES the GitHub Release, not just the git tag. This
script always creates both; a bare `git tag` + push is NOT a release (it never
surfaces on /releases or as Latest). If the gh step fails, fix auth and finish
the release manually before moving on.

This tag push triggers release-formula.yml (syncs the formula in the tap repo
binlecode/homebrew-actop) and publish-pypi.yml (publishes to PyPI via OIDC).
Expand Down Expand Up @@ -122,5 +129,26 @@ git tag "${TAG}"
echo "Pushing tag ${TAG}..."
git push origin "${TAG}"

echo "Done. Tag ${TAG} pushed."
echo "Creating GitHub Release ${TAG}..."
if ! command -v gh >/dev/null 2>&1; then
echo "Error: gh CLI not found — the GitHub Release (required for a release cut) was not created." >&2
echo "The tag was pushed, but the release is incomplete. Install gh and run:" >&2
echo " gh release create \"${TAG}\" --title \"actop ${VERSION}\" --notes \"\$(awk '/^## \\[${VERSION}\\]/ {f=1; next} f && /^## \\[/ {exit} f' CHANGELOG.md)\"" >&2
exit 1
fi

RELEASE_NOTES="$(awk -v ver="${VERSION}" '
$0 ~ "^## \\[" ver "\\]( -.*)?$" { f=1; next }
f && /^## \[/ { exit }
f { print }
' CHANGELOG.md)"

if [[ -z "$RELEASE_NOTES" ]]; then
echo "No CHANGELOG.md section found for ${VERSION}; using generated notes."
gh release create "${TAG}" --generate-notes
else
gh release create "${TAG}" --title "actop ${VERSION}" --notes "${RELEASE_NOTES}"
fi

echo "Done. Tag ${TAG} pushed and GitHub Release created."
echo "Release formula workflow should start from this tag push."