Skip to content
Closed
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
92 changes: 92 additions & 0 deletions .github/scripts/check-version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Version check for FinLens PRs (CLAUDE.md §0 "Finalizing a PR", §17).

Fails when:
1. CHANGELOG.md still has a `## [Unreleased]` entry (the PR isn't finalized yet);
2. the top CHANGELOG version isn't higher than the base branch's (no new version,
or another PR already merged with this number);
3. any version spot from §17 doesn't show the top CHANGELOG version.

Usage: check-version.py [BASE_CHANGELOG]
Run from the repository root. BASE_CHANGELOG is the base branch's CHANGELOG.md;
without it, check 2 is skipped.
"""
import re
import sys

VERSION_HEADING = re.compile(r"^## \[(\d+)\.(\d+)\.(\d+)\]", re.M)
UNRELEASED = re.compile(r"^## \[Unreleased\]", re.M | re.I)

# File, a description for the error message, and a pattern for the version spot.
# Each pattern captures the version written there.
SPOTS = [
("index.html", "sidebar footer", r"v(\d+\.\d+\.\d+) · FinLens"),
("README.md", "header line", r"\*\*Version:\*\* v(\d+\.\d+\.\d+)"),
("README.md", "footer line", r"\*FinLens v(\d+\.\d+\.\d+) —"),
("js/views-settings.js", "General tab", r"FinLens v(\d+\.\d+\.\d+)"),
("CLAUDE.md", "§1 Current version", r"\*\*Current version:\*\* v(\d+\.\d+\.\d+)"),
]


def read(path):
with open(path, encoding="utf-8") as f:
return f.read()


def top_version(text):
m = VERSION_HEADING.search(text)
return tuple(int(x) for x in m.groups()) if m else None


def fmt(v):
return ".".join(map(str, v))


def main():
errors = []
changelog = read("CHANGELOG.md")

if UNRELEASED.search(changelog):
errors.append(
"CHANGELOG.md still has a `## [Unreleased]` entry. This PR isn't finalized: "
"ask Claude to finalize it (merge master in, pick the version, update every "
"version spot), then merge."
)
# The other checks would only compare the previous release's number.
print("Version check failed:")
print(f" - {errors[0]}")
return 1

version = top_version(changelog)
if version is None:
errors.append("CHANGELOG.md has no `## [X.Y.Z]` entry.")
else:
if len(sys.argv) > 1:
base = top_version(read(sys.argv[1]))
if base and version <= base:
errors.append(
f"The top CHANGELOG version v{fmt(version)} isn't higher than the base "
f"branch's v{fmt(base)}. Every change needs a new version (§17); if "
"another PR merged first, merge master in and finalize again."
)
for path, where, pattern in SPOTS:
found = re.findall(pattern, read(path))
if not found:
errors.append(f"{path} ({where}): no version string found.")
elif any(v != fmt(version) for v in found):
errors.append(
f"{path} ({where}) shows v{', v'.join(sorted(set(found)))}, "
f"expected v{fmt(version)}."
)

if errors:
print("Version check failed:")
for e in errors:
print(f" - {e}")
return 1
print(f"Version check passed: v{fmt(version)}, shown in every version spot.")
return 0


if __name__ == "__main__":
sys.exit(main())
24 changes: 24 additions & 0 deletions .github/workflows/version-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Version check

# Blocks merging a PR that isn't finalized (CLAUDE.md §0 "Finalizing a PR", §17):
# fails while CHANGELOG.md says [Unreleased], when the version isn't higher than
# master's, or when a version spot shows a different number.
on:
pull_request:
branches: [master]
types: [opened, synchronize, reopened, edited]

permissions:
contents: read

jobs:
version-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get master's CHANGELOG
run: |
git fetch --depth=1 origin "${{ github.base_ref }}"
git show FETCH_HEAD:CHANGELOG.md > "$RUNNER_TEMP/base-CHANGELOG.md"
- name: Check version
run: python3 .github/scripts/check-version.py "$RUNNER_TEMP/base-CHANGELOG.md"
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

---

## [Unreleased]

### Technical
- **Version check on every PR into `master`.** A GitHub Actions check (`.github/workflows/version-check.yml`, running `.github/scripts/check-version.py`) fails when the PR isn't finalized. It catches three cases:
- CHANGELOG.md still has a `## [Unreleased]` entry;
- the top version isn't higher than `master`'s, either because nothing was bumped or because another PR merged first with the same number;
- the sidebar footer, README, Settings → General or CLAUDE.md §1 shows a different version.
- **CLAUDE.md §17** describes the check.

---

## [1.10.5] — 2026-11-02

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,8 @@ Bump the patch version for bug fixes, minor version for new features, major vers

Also update **Current version** in §1 of this file. Never close out a change without updating all of these.

**Version check (GitHub Actions).** `.github/workflows/version-check.yml` runs `.github/scripts/check-version.py` on every PR into `master`. It fails while CHANGELOG has a `## [Unreleased]` entry, when the top version isn't higher than `master`'s, or when any spot above (plus §1) shows a different number. A red check means the PR isn't finalized: don't merge it. If you add or move a version spot, update `SPOTS` in the script.

---

## 18. Supported Account Types
Expand Down
Loading