diff --git a/.github/workflows/build-explainers.yml b/.github/workflows/build-explainers.yml index 61e8659..232c857 100644 --- a/.github/workflows/build-explainers.yml +++ b/.github/workflows/build-explainers.yml @@ -8,7 +8,10 @@ name: Build Explainers # explainer - not byte- or pixel-compared against this job's own fresh # regeneration, since Pillow's bundled FreeType renders text with # genuinely different pixels across platforms (see -# scripts/check_generated_files_current.py's docstring). +# scripts/check_generated_files_current.py's docstring). Also verifies the +# plain-prose "N explainers" mentions in README.md, CONTRIBUTORS.md, +# METRICS.md, and ROADMAP.md haven't drifted from the real count of +# explainers/*.md (scripts/check_explainer_count.py). # # main requires a PR (branch protection), and github-actions[bot] isn't in # that ruleset's bypass list, so this can no longer auto-commit a fix by @@ -27,6 +30,11 @@ on: - "scripts/build_explainers.py" - "scripts/generate_og_images.py" - "scripts/check_generated_files_current.py" + - "scripts/check_explainer_count.py" + - "README.md" + - "CONTRIBUTORS.md" + - "METRICS.md" + - "ROADMAP.md" pull_request: paths: - "explainers/**.md" @@ -35,6 +43,11 @@ on: - "scripts/build_explainers.py" - "scripts/generate_og_images.py" - "scripts/check_generated_files_current.py" + - "scripts/check_explainer_count.py" + - "README.md" + - "CONTRIBUTORS.md" + - "METRICS.md" + - "ROADMAP.md" jobs: check: @@ -67,3 +80,6 @@ jobs: # existence/size/dimensions instead, which is platform-independent. # Text-based generated files still get an exact git diff. run: python3 scripts/check_generated_files_current.py + + - name: Fail if a "current explainer count" mention has drifted + run: python3 scripts/check_explainer_count.py diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 439453f..59fde31 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -55,7 +55,7 @@ the [Git identity map](#git-identity-map) so `git shortlog -sne` can be reconcil | | Who | Role | |:--|-----|------| -| yakew7 | **Yash Kewlani** - [@yakew7](https://github.com/yakew7) | Creator and maintainer. Author of the seven audits, the `faircode` library and benchmark harness, the Open Dataset Profiler, the website, and the bulk of the 39 explainers. Code owner for `faircode/`, `paper/`, every `audit.yaml`, and project policy (`CLAUDE.md`, `CONTRIBUTING.md`); co-owner of `explainers/`. | +| yakew7 | **Yash Kewlani** - [@yakew7](https://github.com/yakew7) | Creator and maintainer. Author of the seven audits, the `faircode` library and benchmark harness, the Open Dataset Profiler, the website, and the bulk of the 53 explainers. Code owner for `faircode/`, `paper/`, every `audit.yaml`, and project policy (`CLAUDE.md`, `CONTRIBUTING.md`); co-owner of `explainers/`. | Contact: [yashkewlani2020@gmail.com](mailto:yashkewlani2020@gmail.com) ยท [@thefaircodeproject](https://instagram.com/thefaircodeproject) diff --git a/README.md b/README.md index bb4cc04..5d6c169 100644 --- a/README.md +++ b/README.md @@ -1166,7 +1166,7 @@ The full public roadmap - with phases, completion status, and content schedule - | Combined Social Reach (Instagram + LinkedIn) | 30K+ | | Countries Reached (Website Visitors) | 18 | | Code Audits Published | 7 | -| Explainers Published | 47 | +| Explainers Published | 53 | Tracked weekly in [METRICS.md](METRICS.md). diff --git a/scripts/check_explainer_count.py b/scripts/check_explainer_count.py new file mode 100644 index 0000000..e393092 --- /dev/null +++ b/scripts/check_explainer_count.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Fails if a "current explainer count" mention in README.md, CONTRIBUTORS.md, +METRICS.md, or ROADMAP.md drifts from the real count of explainers/*.md. + +CHECKS below is a small, explicit list of exact regexes, one per file - not a +fuzzy "N explainers" scan across the whole file. METRICS.md in particular is a +weekly log full of legitimate historical counts (e.g. "explainer count `39 -> +44`" from an old week); matching those against today's count would be a false +positive worse than the drift this script is meant to catch. Each regex below +targets the one line in its file that's meant to state the current, live +total. + +Run locally: python3 scripts/check_explainer_count.py +Exit code: 0 = all mentions match, 1 = at least one is stale or missing. +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +EXPLAINERS_DIR = ROOT / "explainers" + +# (relative path, regex whose sole capture group is the claimed count) +CHECKS = [ + ("README.md", re.compile(r"Show all (\d+) explainers")), + ("CONTRIBUTORS.md", re.compile(r"the bulk of the (\d+) explainers")), + ("METRICS.md", re.compile(r"Explainers-(\d+)-blueviolet")), + ("ROADMAP.md", re.compile(r"(\d+) explainers published")), +] + + +def main() -> int: + actual = len(list(EXPLAINERS_DIR.glob("*.md"))) + stale = [] + + for rel_path, pattern in CHECKS: + path = ROOT / rel_path + text = path.read_text(encoding="utf-8") + match = pattern.search(text) + if match is None: + stale.append((rel_path, f"expected pattern not found: {pattern.pattern!r}")) + continue + claimed = int(match.group(1)) + if claimed != actual: + stale.append((rel_path, f"says {claimed} explainers, but explainers/*.md has {actual}")) + + if stale: + print(f"Explainer count drift found (explainers/*.md currently has {actual} files):", + file=sys.stderr) + for rel_path, reason in stale: + print(f" {rel_path}: {reason}", file=sys.stderr) + return 1 + + print(f"OK: all {len(CHECKS)} explainer-count mentions match explainers/*.md ({actual} files).") + return 0 + + +if __name__ == "__main__": + sys.exit(main())