diff --git a/.github/workflows/check-go-version.yml b/.github/workflows/check-go-version.yml new file mode 100644 index 0000000..f609b23 --- /dev/null +++ b/.github/workflows/check-go-version.yml @@ -0,0 +1,79 @@ +name: Check Go Version + +on: + schedule: + # Run every Monday at 9 AM UTC + - cron: '0 9 * * 1' + workflow_dispatch: # Allow manual triggering + +permissions: + issues: write + +jobs: + check-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Get current Go version from go.mod + id: current + run: | + # go.mod is the canonical and only source of truth for the repository's + # Go version. Workflows derive their version from it via + # `go-version-file: go.mod`, so there is nothing else to update. + CURRENT_VERSION=$(grep -E '^go [0-9]' go.mod | awk '{print $2}') + echo "Current Go version in go.mod: $CURRENT_VERSION" + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" + + - name: Get latest stable Go version + id: latest + run: | + # Fetch the latest stable Go version from the official releases page. + LATEST_VERSION=$(curl -s https://go.dev/VERSION?m=text | head -n 1 | sed 's/go//') + echo "Latest stable Go version: $LATEST_VERSION" + echo "version=$LATEST_VERSION" >> "$GITHUB_OUTPUT" + + - name: Open an issue if outdated + env: + GH_TOKEN: ${{ github.token }} + CURRENT_VERSION: ${{ steps.current.outputs.version }} + LATEST_VERSION: ${{ steps.latest.outputs.version }} + run: | + echo "Current: $CURRENT_VERSION" + echo "Latest: $LATEST_VERSION" + + if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then + echo "Go version is up to date!" + exit 0 + fi + + echo "Go version is outdated." + TITLE="Update Go version from $CURRENT_VERSION to $LATEST_VERSION" + + # Skip if an open issue for this exact bump already exists. Opening an + # issue (rather than building the PR here) keeps this workflow simple and + # lets the autonomous Claude agent produce the bump PR. + EXISTING_ISSUE=$(gh issue list --search "$TITLE in:title" --state open --label claude --json number,title --jq "map(select(.title == \"$TITLE\")) | .[0].number // empty") + if [ -n "$EXISTING_ISSUE" ]; then + echo "Issue #$EXISTING_ISSUE already open for this bump, skipping." + exit 0 + fi + + gh issue create \ + --title "$TITLE" \ + --label "claude" \ + --body "$(cat <.go` (for example `ecs_world.go`, `ecs_store.go`), with matching tests `ecs__test.go`. The package doc lives in `ecs/ecs_doc.go`. * Keep files small and single-responsibility; split by responsibility, not by technical layer. -### Go version and dependencies +### Go Version Consistency + +`go.mod` is the single source of truth for this repository's Go version — it is the only place a Go version is written, and the workflows already derive their version from it via `go-version-file: go.mod`: + +```yaml +- uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true +``` + +Never hardcode a `go-version:` literal in a workflow. The scheduled `check-go-version.yml` workflow compares `go.mod`'s declared version against the latest stable Go release and, when it falls behind, opens a `claude`-labelled issue requesting the bump; the autonomous Claude agent then makes the one-line change to the single `go` directive. Because nothing else carries the version, a bump is a one-line change. + +### Dependencies -* Go 1.26 (see `go.mod`). The code relies on range-over-func and the `iter` package. * No third-party dependencies, and that is a deliberate property of a foundational library. A new dependency is a significant decision; if one is ever genuinely needed, call it out prominently in the PR description (name, version, purpose, justification, and whether it is test-only). ### Verification before pushing