# How README Counts Are Auto-Synced by CI on Merge in AI Engineering Curriculum

> Learn how CI automatically syncs README counts on merge for the AI Engineering Curriculum repository. Discover the automated process that ensures accurate statistics.

- Repository: [Rohit Ghumare/ai-engineering-from-scratch](https://github.com/rohitg00/ai-engineering-from-scratch)
- Tags: how-to-guide
- Published: 2026-06-17

---

**When a commit lands on the `main` branch, the `readme-counts-sync` CI job automatically regenerates the curriculum catalog and rewrites any mismatched statistics in [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md) before committing the fixes back to the repository.**

The `rohitg00/ai-engineering-from-scratch` repository maintains dynamic statistics—such as lesson counts, phase totals, and artifact numbers—directly in its [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md). To ensure these hard-coded values remain accurate as the curriculum evolves, the project employs a continuous integration pipeline that automatically synchronizes README counts on every merge to `main`.

## The Auto-Sync Pipeline Architecture

The synchronization process relies on three coordinated components: a catalog generator, a README validator, and a GitHub Actions workflow.

### Source of Truth Generation

The [`scripts/build_catalog.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/scripts/build_catalog.py) utility serves as the authority for curriculum metrics. It traverses the `phases/` directory structure, enumerating every lesson, code file, and output artifact. The script outputs a [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json) file containing a `totals` block with fields like `lessons`, `phases`, `skills`, `prompts`, and `artifacts`.

### README Validation and Patching

The [`scripts/check_readme_counts.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/scripts/check_readme_counts.py) script performs the actual synchronization. When invoked with the `--fix` flag, it reads both [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json) and [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md), then applies targeted regular-expression replacements to align the documented counts with the generated totals.

The core logic resides in the `apply_fixes` function:

```python

# scripts/check_readme_counts.py

def apply_fixes(readme_text: str, totals: dict[str, int]) -> str:
    for pattern in PATTERNS:
        expected = totals[pattern.field]

        def replace(match: re.Match[str], expected: int = expected) -> str:
            whole = match.group(0)
            old = match.group(1)
            start = match.start(1) - match.start()
            return whole[:start] + str(expected) + whole[start + len(old):]

        readme_text = pattern.regex.sub(replace, readme_text)
    return readme_text

```

### Automated Commit Workflow

The [`.github/workflows/curriculum.yml`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/.github/workflows/curriculum.yml) file defines the `readme-counts-sync` job, which executes only on `push` events to `main`. The job requires `contents: write` permissions to push automated commits.

```yaml

# .github/workflows/curriculum.yml

- name: readme-counts-sync
  runs-on: ubuntu-latest
  permissions:
    contents: write
  if: github.event_name == 'push'
  steps:
    - uses: actions/checkout@v4
      with:
        ref: ${{ github.ref }}
        token: ${{ secrets.GITHUB_TOKEN }}
    - uses: actions/setup-python@v5
      with:
        python-version: "3.12"
    - name: build ephemeral catalog
      run: python3 scripts/build_catalog.py
    - name: sync README counts
      run: python3 scripts/check_readme_counts.py --fix
    - name: commit + push if README changed
      env:
        BOT_COMMIT_PREFIX: "chore(readme): sync counts"
      run: |
        if git diff --quiet README.md; then exit 0; fi
        git config user.name "github-actions[bot]"
        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
        git add README.md
        git commit -m "$BOT_COMMIT_PREFIX"
        git push origin HEAD:${{ github.ref#refs/heads/ }}

```

## Step-by-Step Execution Flow

When a pull request merges into `main`, the following sequence ensures the README stays current:

1. **Catalog Generation** – The workflow executes `python3 scripts/build_catalog.py` to scan the `phases/` directory and produce an updated [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json) with current totals.
2. **Count Synchronization** – The command `python3 scripts/check_readme_counts.py --fix` reads the catalog and rewrites any divergent numbers in [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md) using regex patterns that match badge URLs, alt-text, and prose snippets.
3. **Conditional Committing** – The workflow checks if [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md) was modified. If unchanged, the job exits silently. If changed, it stages the file, commits with the message `chore(readme): sync counts`, and pushes to the `main` branch.
4. **Self-Healing Verification** – Subsequent pushes find the README already synchronized, resulting in no-op runs that conserve CI resources.

## Preventing Drift in Pull Requests

To provide early feedback, the repository includes a separate `readme-counts-drift` job that runs on pull request branches. This job executes the check without the `--fix` flag, warning authors that the `main` branch will automatically correct any discrepancies post-merge. This dual-job approach allows developers to preview changes while guaranteeing the source of truth remains accurate.

## Summary

- The `readme-counts-sync` job in [`.github/workflows/curriculum.yml`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/.github/workflows/curriculum.yml) triggers exclusively on pushes to `main`, including merge events.
- [`scripts/build_catalog.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/scripts/build_catalog.py) generates authoritative metrics by scanning the `phases/` directory structure and writing to [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json).
- `scripts/check_readme_counts.py --fix` uses regex pattern matching to update hard-coded statistics in [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md) without manual intervention.
- Automated commits use the prefix `chore(readme): sync counts` and only occur when the README actually changes, creating a self-healing documentation system.

## Frequently Asked Questions

### What triggers the README count synchronization?

The synchronization triggers on any `push` event to the `main` branch, which includes merged pull requests. The job is explicitly gated by the condition `if: github.event_name == 'push'` in the workflow configuration.

### How does the script know which numbers to update in the README?

The [`check_readme_counts.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/check_readme_counts.py) script uses a predefined set of regular-expression **patterns** that target specific fields—such as badge URLs, alt-text attributes, and inline prose—associated with lesson, phase, and artifact counts. Each pattern maps to a field in the `totals` block of [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json).

### Can I run the count synchronization locally before pushing?

Yes. Execute `python3 scripts/build_catalog.py` followed by `python3 scripts/check_readme_counts.py --fix` from the repository root. This regenerates [`catalog.json`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/catalog.json) and updates [`README.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/README.md) in-place, allowing you to verify the exact changes that CI would apply.

### What happens if the README is already correct?

If `git diff --quiet README.md` detects no changes after running the sync scripts, the workflow exits immediately without creating a commit. This idempotent behavior ensures the git history remains clean and free from unnecessary automated commits.