diff --git a/generated/badges.readme.md b/generated/badges.readme.md
index 920799e..3535386 100644
--- a/generated/badges.readme.md
+++ b/generated/badges.readme.md
@@ -1,15 +1,15 @@
+
-
+
+
-
-
diff --git a/tests/test_docs_audit.py b/tests/test_docs_audit.py
index e9226e7..42ed78d 100644
--- a/tests/test_docs_audit.py
+++ b/tests/test_docs_audit.py
@@ -391,6 +391,29 @@ def test_a_github_blob_url_into_either_repository_is_an_internal_link(tmp_path):
]
+def test_a_link_into_the_badges_branch_is_external(tmp_path):
+ """The `badges` branch is an orphan CI writes and no checkout holds, so a link into it is
+ somebody else's to keep however internal the URL looks. `clones-history.json` is the
+ receipt the README's clones badge links to: resolved against a worktree that holds `main`
+ it is a working link reported as broken, and the rest of the branch must stay skipped with
+ it. A path missing on a ref this checkout *does* hold is still broken, which is the half
+ that fails if the skip is widened to every absolute github.com URL.
+ """
+ page = tmp_path / "a.md"
+ page.write_text(
+ "[clones](https://github.com/CTRLRun/ctrlrun/blob/badges/clones-history.json)\n"
+ "[tests](https://github.com/CTRLRun/ctrlrun/blob/badges/tests-badge.json)\n"
+ "[bad](https://github.com/CTRLRun/ctrlrun/blob/main/clones-history.json)\n",
+ encoding="utf-8",
+ )
+
+ broken = links.check_text(page.read_text(), page)
+
+ assert [b.target for b in broken] == [
+ "https://github.com/CTRLRun/ctrlrun/blob/main/clones-history.json"
+ ]
+
+
def test_a_root_relative_docs_path_resolves_under_docs(tmp_path, monkeypatch):
monkeypatch.setattr(links, "REPO_ROOT", tmp_path)
(tmp_path / "docs" / "concepts").mkdir(parents=True)
diff --git a/tools/docs_audit/links.py b/tools/docs_audit/links.py
index 20ec7b3..5b2004b 100644
--- a/tools/docs_audit/links.py
+++ b/tools/docs_audit/links.py
@@ -8,7 +8,8 @@
- `[text](/path)` and `href="/path"` — the docs site's own root-relative form, resolved against
`docs/` with `.mdx` then `.md` appended, and against the repository root as a fallback;
- `https://github.com/CTRLRun/ctrlrun/blob/[/` and `/tree/][/`, which are
- internal links wearing an absolute URL, resolved against the checkout.
+ internal links wearing an absolute URL, resolved against the checkout. A `][` no checkout
+ holds is skipped instead: the `badges` branch is an orphan CI writes and nobody clones.
Every other absolute URL is skipped: an external link is somebody else's to keep, and a check
that opened sockets would be a check that failed in CI for reasons nobody here can fix.
@@ -49,8 +50,13 @@ def documents_to_check() -> list[Path]:
#: repository, so a page can cite `LICENSE` in the library and `docs.json` here and
#: both are checked. Named groups rather than two regexes: one place decides.
_GITHUB = re.compile(
- r"^https://github\.com/CTRLRun/(?Pctrlrun|ctrlrun-docs)/(?:blob|tree)/[^/]+/(?P.*)$"
+ r"^https://github\.com/CTRLRun/(?Pctrlrun|ctrlrun-docs)/(?:blob|tree)/(?P][[^/]+)/(?P.*)$"
)
+#: Refs no checkout contains, so a link into one is external however much it looks internal.
+#: `badges` is the orphan branch CI publishes the endpoint documents to: `clones-history.json`
+#: is real and is the receipt the clones badge links to, and resolving it against a worktree
+#: that holds `main` would report a working link as broken.
+_DETACHED_REFS: frozenset[str] = frozenset({"badges"})
_HEADING = re.compile(r"^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$")
_EXPLICIT_ID = re.compile(r"\{#([A-Za-z0-9_-]+)\}\s*$")
_ID_ATTRIBUTE = re.compile(r"""\bid=["']([A-Za-z0-9_-]+)["']""")
@@ -133,6 +139,8 @@ def _resolve(target: str, source: Path) -> tuple[Path | None, str | None] | None
matched = _GITHUB.match(path_part)
if matched is None:
return None
+ if matched.group("ref") in _DETACHED_REFS:
+ return None
root = REPO_ROOT if matched.group("repo") == "ctrlrun-docs" else CORE_ROOT
return root / unquote(matched.group("path")), anchor
if not path_part:
diff --git a/tools/docs_audit/render_badges.py b/tools/docs_audit/render_badges.py
index 880af1f..3c71344 100644
--- a/tools/docs_audit/render_badges.py
+++ b/tools/docs_audit/render_badges.py
@@ -17,13 +17,30 @@
- `tests-badge.json`, written by `--write-count` **after** `scripts/check.sh` has passed, so no
number is published for a run whose suite was red.
-Two are static, and a static badge is a self-assertion unless something enforces it. These two
-are enforced: `scripts/check.sh` runs `ruff format --check`, `ruff check` and `mypy --strict
-src`, CI calls that file rather than naming the tools itself, and `test_ci_runs_the_check_script`
-fails if it stops. The `docs` badge is a link and claims nothing.
-
-Downloads and stars are deliberately absent. `STYLE.md` forbids social proof that does not
-exist, and a count published four days after the first release measures mirrors.
+One badge is static: `docs`, which is a link and claims nothing. The row carried `ruff` and
+`mypy --strict` as well until 2026-09-11. Both were enforced rather than asserted, and both are
+still enforced now that the badges are gone: `scripts/check.sh` runs `ruff format --check`,
+`ruff check` and `mypy --strict src`, CI calls that file rather than naming the tools itself,
+and `test_ci_runs_the_check_script` fails if it stops. They were removed because how a library
+is written is not what a stranger decides in the first five seconds, and because a row that
+wraps to three lines on a phone is a row nobody reads to the end. `pypi/pyversions` went with
+them: it was metadata rather than a claim, and the PyPI page the badge beside it links to
+carries it anyway.
+
+Stars are deliberately absent: `STYLE.md` forbids social proof that does not exist, and a star
+count is a popularity number with no reading behind it. The two adoption counts that are here
+each name what they measure and link to the data rather than to a page that repeats them:
+
+- `downloads` is PyPI's own monthly figure via shields.io, which reads pypistats. It counts
+ installs by mirrors and by CI as well as by people, so it is an upper bound on adoption and
+ is labelled `downloads/month` rather than users.
+- `clones` is this repository's own, published to the `badges` branch by `.github/workflows/
+ traffic.yml`. GitHub's traffic API keeps fourteen days and needs push access, so the workflow
+ reads it daily with a token, merges each day into `clones-history.json` on that branch, and
+ the badge links to that file: the number is a sum of days anybody can re-add. It counts every
+ `git clone`, and `actions/checkout` is one, so this repository's own CI is in the figure
+ alongside everybody else's; a reader who wants people rather than clones has the per-day
+ file and the workflow run history to subtract with.
`--write-count` is what CI calls, and what it counts is what `pytest` **collects**. That is not
the same as what passed: the suite skips a handful of tests on a machine without a framework
@@ -61,19 +78,26 @@ class Badge:
href: str
-#: The row, in reading order: what it is, where it is documented, that it builds and is
-#: analysed, that its own suite is this big, that its guarantees were checked, how its supply
-#: chain scores, how it is written, and the licence.
+#: The row, in reading order: how often it is cloned, what it is, how often it is installed,
+#: where it is documented, that it builds, is analysed and is fuzzed, that its own suite is this
+#: big, that its guarantees were checked, how its supply chain scores, and the licence. Every entry is a
+#: claim a reader can follow to the thing that measured it; an entry that is not stops being a
+#: badge and becomes decoration, which is the test the three removed ones failed.
BADGES: tuple[Badge, ...] = (
+ Badge(
+ "Clones",
+ f"https://img.shields.io/endpoint?url={BADGES_BRANCH}/clones-badge.json",
+ "https://github.com/CTRLRun/ctrlrun/blob/badges/clones-history.json",
+ ),
Badge(
"PyPI",
"https://img.shields.io/pypi/v/ctrlrun?color=B8730A&label=pypi",
"https://pypi.org/project/ctrlrun/",
),
Badge(
- "Python versions",
- "https://img.shields.io/pypi/pyversions/ctrlrun?color=B8730A",
- "https://pypi.org/project/ctrlrun/",
+ "Downloads",
+ "https://img.shields.io/pypi/dm/ctrlrun?color=B8730A&label=downloads",
+ "https://pypistats.org/packages/ctrlrun",
),
Badge(
"Docs",
@@ -90,6 +114,11 @@ class Badge:
"https://github.com/CTRLRun/ctrlrun/actions/workflows/codeql.yml/badge.svg?branch=main",
"https://github.com/CTRLRun/ctrlrun/actions/workflows/codeql.yml",
),
+ Badge(
+ "Fuzz",
+ "https://github.com/CTRLRun/ctrlrun/actions/workflows/fuzz.yml/badge.svg?branch=main",
+ "https://github.com/CTRLRun/ctrlrun/actions/workflows/fuzz.yml",
+ ),
Badge(
"Tests",
f"https://img.shields.io/endpoint?url={BADGES_BRANCH}/tests-badge.json",
@@ -105,16 +134,6 @@ class Badge:
"https://api.scorecard.dev/projects/github.com/CTRLRun/ctrlrun/badge",
"https://scorecard.dev/viewer/?uri=github.com/CTRLRun/ctrlrun",
),
- Badge(
- "Ruff",
- "https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json",
- "https://github.com/astral-sh/ruff",
- ),
- Badge(
- "Checked with mypy --strict",
- "https://img.shields.io/badge/mypy-strict-B8730A",
- "https://github.com/CTRLRun/ctrlrun/blob/main/scripts/check.sh",
- ),
Badge(
"License",
"https://img.shields.io/pypi/l/ctrlrun?color=B8730A",
]