Skip to content
Open
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
79 changes: 79 additions & 0 deletions .github/workflows/update-badges.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Update Badges

on:
workflow_run:
workflows: ["Test Suite"]
types: [completed]
branches: [main]

permissions:
contents: write

jobs:
update-badges:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 2

- name: Check if last commit was a badge update
id: skip-check
run: |
LAST_MSG=$(git log -1 --pretty=%B)
if echo "$LAST_MSG" | grep -qiE '^(ci|chore)\(.*badge\)|^\[skip badges\]|^chore: update badge'; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "Skipping: last commit was a badge update ($LAST_MSG)"
else
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: Count tests
id: count
if: steps.skip-check.outputs.skip != 'true'
run: |
TEST_COUNT=$(grep -rE '^\s*@test\s' tests/unit/ tests/integration/ | wc -l)
TEST_FILES=$(find tests/unit/ tests/integration/ -name "*.bats" | wc -l)
Comment on lines +37 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] The count excludes tests/e2e/, so the badge advertises fewer tests than the suite actually runs and than the README's own prose claims.

Failure scenario: grep over tests/unit/ tests/integration/ yields 1279, but the real total including tests/e2e/ is 1297 (the @test regex itself is fine — the only lines it "misses" are email addresses like e2e@test.local). README line 1201, which this workflow is meant to keep accurate, explicitly states the count covers "unit, integration, and end-to-end," so writing 1279 onto it makes that sentence false. Either include tests/e2e/ or drop "end-to-end" from the README wording.

Suggested change
TEST_COUNT=$(grep -rE '^\s*@test\s' tests/unit/ tests/integration/ | wc -l)
TEST_FILES=$(find tests/unit/ tests/integration/ -name "*.bats" | wc -l)
TEST_COUNT=$(grep -rE '^\s*@test\s' tests/unit/ tests/integration/ tests/e2e/ | wc -l)
TEST_FILES=$(find tests/unit/ tests/integration/ tests/e2e/ -name "*.bats" | wc -l)

echo "TEST_COUNT=$TEST_COUNT" >> $GITHUB_ENV
echo "TEST_FILES=$TEST_FILES" >> $GITHUB_ENV
echo "test_count=$TEST_COUNT" >> $GITHUB_OUTPUT
echo "test_files=$TEST_FILES" >> $GITHUB_OUTPUT
echo "Tests: $TEST_COUNT across $TEST_FILES files"

- name: Update README.md test badge
if: steps.skip-check.outputs.skip != 'true'
run: |
sed -i "s|https://img.shields.io/badge/tests-[0-9]*%20passing|https://img.shields.io/badge/tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s|tests-[0-9]*%20passing|tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s/\*\*Test Coverage\*\*: [0-9]* tests/\*\*Test Coverage\*\*: ${TEST_COUNT} tests/g" README.md
sed -i "s/\*\*[0-9]* tests\*\* across/\*\*${TEST_COUNT} tests\*\* across/g" README.md
Comment on lines +48 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] Two README test-count references use patterns that no sed here matches, so they stay stale while the badge and the other lines are rewritten — the README ends up displaying two different test counts at once.

Failure scenario: README currently shows 784 at line 1069 (# All 784 tests must pass) and line 1201 (- **784 tests across 34 test files**). After this workflow runs, the badge and lines 6/21/960 flip to the new count (e.g. 1279) but 1069 and 1201 keep 784, so the document contradicts itself. Line 1201's **N tests across lacks the inner ** that the line-51 regex requires, and All N tests isn't matched by any pattern.

Suggested change
sed -i "s|https://img.shields.io/badge/tests-[0-9]*%20passing|https://img.shields.io/badge/tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s|tests-[0-9]*%20passing|tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s/\*\*Test Coverage\*\*: [0-9]* tests/\*\*Test Coverage\*\*: ${TEST_COUNT} tests/g" README.md
sed -i "s/\*\*[0-9]* tests\*\* across/\*\*${TEST_COUNT} tests\*\* across/g" README.md
sed -i "s|https://img.shields.io/badge/tests-[0-9]*%20passing|https://img.shields.io/badge/tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s|tests-[0-9]*%20passing|tests-${TEST_COUNT}%20passing|g" README.md
sed -i "s/\*\*Test Coverage\*\*: [0-9]* tests/\*\*Test Coverage\*\*: ${TEST_COUNT} tests/g" README.md
sed -i "s/\*\*[0-9]* tests\*\* across/\*\*${TEST_COUNT} tests\*\* across/g" README.md
# also catch "**N tests across" (no inner **) and "All N tests", else those refs drift
sed -i "s/\*\*[0-9]* tests across/\*\*${TEST_COUNT} tests across/g" README.md
sed -i "s/All [0-9]* tests/All ${TEST_COUNT} tests/g" README.md


- name: Update CLAUDE.md test references
if: steps.skip-check.outputs.skip != 'true'
run: |
# Update any test count text in CLAUDE.md (if present)
sed -i "s/\*\*Test Coverage\*\*: [0-9]* tests/\*\*Test Coverage\*\*: ${TEST_COUNT} tests/g" CLAUDE.md || true
sed -i "s/[0-9]* tests, 100% pass rate/${TEST_COUNT} tests, 100% pass rate/g" CLAUDE.md || true

- name: Check for changes
id: changes
if: steps.skip-check.outputs.skip != 'true'
run: |
if git diff --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
echo "No badge changes needed."
else
echo "changed=true" >> $GITHUB_OUTPUT
git diff
fi

- name: Commit and push changes
if: steps.skip-check.outputs.skip != 'true' && steps.changes.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md CLAUDE.md
git commit -m "ci(badge): update test count to ${TEST_COUNT} (${TEST_FILES} files)"
git push

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] With no concurrency group, two near-simultaneous merges each complete "Test Suite" and fire this workflow; both jobs compute and push, and the second git push is rejected (non-fast-forward), failing the job and dropping that badge update until the next push.

Failure scenario: PR A and PR B land on main within a minute → two workflow_run triggers → two update-badges jobs run concurrently → job 1 pushes ci(badge):… → job 2's git push exits non-zero → job 2 fails (red ❌) and its count update is silently lost (self-heals only on the next real push). Fix: add a job-level concurrency: group (e.g. group: update-badges, cancel-in-progress: false) so the pushes serialize.

119 changes: 119 additions & 0 deletions tasks/todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Issue #138: feat: automate version and test count badges via GitHub Actions

## Plan

## Problem

Version badges in README.md and CLAUDE.md require manual updates, leading to drift between actual state and documented state. This was caught during PR #137 review where badges showed `v0.10.1` and `310 tests` instead of `v0.11.2` and `440 tests`.

## Proposed Solution

Implement a GitHub Actions workflow that automatically updates badges after:
1. Test suite runs (update test count)
2. Releases are published (update version)

### Implementation Plan

**Phase 1: Test Count Automation**

Create `.github/workflows/update-badges.yml`:

```yaml
name: Update Badges

on:
push:
branches: [main]
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]

jobs:
update-badges:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'push'
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Get test count
run: |
TEST_COUNT=$(npm test 2>&1 | grep -c "^ok " || echo "0")
echo "TEST_COUNT=$TEST_COUNT" >> $GITHUB_ENV

- name: Update badges if changed
run: |
# Update README.md test badge
sed -i "s/tests-[0-9]*%20passing/tests-${TEST_COUNT}%20passing/" README.md

# Update CLAUDE.md test count
sed -i "s/Tests\*\*: [0-9]* passing/Tests**: ${TEST_COUNT} passing/" CLAUDE.md

- name: Check for changes
id: changes
run: |
if git diff --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi

- name: Commit changes
if: steps.changes.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md CLAUDE.md
git commit -m "chore: update test count badge to ${TEST_COUNT}"
git push
```

**Phase 2: Version Automation (Future)**

When GitHub Releases are adopted:
- Trigger on `release: [published]`
- Extract version from release tag
- Update version badges in README.md, CLAUDE.md
- Consider using shields.io dynamic badges: `![Version](https://img.shields.io/github/v/release/frankbria/ralph-claude-code)`

### Files to Update

| File | Badge Type | Current Format |
|------|-----------|----------------|
| README.md | Version | `![Version](https://img.shields.io/badge/version-X.X.X-blue)` |
| README.md | Tests | `![Tests](https://img.shields.io/badge/tests-XXX%20passing-green)` |
| CLAUDE.md | Both | `**Version**: vX.X.X \| **Tests**: XXX passing` |

### Acceptance Criteria

- [ ] Workflow runs after successful CI on main branch
- [ ] Test count badge updates automatically
- [ ] No workflow runs on badge-only commits (prevent infinite loop)
- [ ] Commit messages follow conventional commit format

### Future Enhancements

- [ ] Integrate with GitHub Releases for version automation
- [ ] Consider switching to dynamic shields.io badges
- [ ] Add badge for code coverage percentage

## Related

- PR #137 - Manual badge update that prompted this issue

## Acceptance Criteria

- [ ] Workflow runs after successful CI on main branch
- [ ] Test count badge updates automatically
- [ ] No workflow runs on badge-only commits (prevent infinite loop)
- [ ] Commit messages follow conventional commit format