From 5709732870b234e398d9b52f1727ea4b6fef9f15 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 15:56:23 +0000 Subject: [PATCH] docs(readme): make the README's claims true, and gate them in CI The README's structural claims had drifted from the tree. EXPLAINME recorded every one of them; this commit fixes them at the source. - Counts: - rule modules 35 -> 42; - lib/ 133 -> 154 source files; - Just recipes 39 -> 45 (`just --summary`); - CLI commands five -> six (secret-alerts); - Mix tasks "eleven more" -> twelve. - `just build-all` builds the Rust workspace and Elixir. The README now lists build-ffi and compile-abi beside it. - No guix.scm is tracked, so the `guix shell -m guix.scm` step is removed. The README and AFFIRMATION say so and name mise.toml and deploy/Containerfile as the stand-ins. - The rule-distillation loop is the design. The README now says the rules are hand-written and the neural layer can only lower confidence. - The /api/* policy is stated as api_router.ex applies it: bearer when HYPATIA_API_BEARER_TOKEN is set, else loopback unless HYPATIA_API_ALLOW_NONLOCAL=true. - Dead links: topology.md and DEBT-REGISTER.md (both .adoc), CONTRIBUTING.md (.github/), and 17 .md -> .adoc links in docs/README.adoc. - EXPLAINME quotes the corrected text, and its known-gaps note keeps only what is still true. scripts/check-readme-claims.sh measures the gated counts and resolves every local link in the README, EXPLAINME and the docs index. It runs in the Validate Documentation job and fails on drift; the older link step there only warned, which is how the 17 links survived. tests/readme-claims-test.sh plants a stale count of each kind and a dead link (6/6), and checks that the recipe count agrees with and without `just` on PATH. Validation: - actionlint: no new findings (the reported issues are in existing steps); - shellcheck: clean; - asciidoctor: README, EXPLAINME, AFFIRMATION and docs/README render without warnings; - proof-ledger tests 34/34, and `claims` agrees. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM --- .github/workflows/quality.yml | 8 ++++ AFFIRMATION.adoc | 8 ++-- CHANGELOG.adoc | 25 +++++++++++ EXPLAINME.adoc | 75 +++++++++++++++++---------------- README.adoc | 42 ++++++++++++------- docs/README.adoc | 36 ++++++++-------- scripts/check-readme-claims.sh | 76 ++++++++++++++++++++++++++++++++++ tests/readme-claims-test.sh | 61 +++++++++++++++++++++++++++ 8 files changed, 259 insertions(+), 72 deletions(-) create mode 100755 scripts/check-readme-claims.sh create mode 100755 tests/readme-claims-test.sh diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a92a7833..ddc37aad 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -270,6 +270,14 @@ jobs: done done + # The front-door documents fail on drift rather than warn: the counts the + # README quotes are measured from the tree, and every local link in the + # README, EXPLAINME and the docs index must resolve. + - name: README counts and front-door links match the tree + run: | + bash tests/readme-claims-test.sh + bash scripts/check-readme-claims.sh + # ============================================================================ # Deploy to GitHub Pages # ============================================================================ diff --git a/AFFIRMATION.adoc b/AFFIRMATION.adoc index 7b8eee48..67a1f0ca 100644 --- a/AFFIRMATION.adoc +++ b/AFFIRMATION.adoc @@ -16,9 +16,11 @@ state and commitments. Companion detail: == We affirm (current normative state — MUST) -* *Guix-only builds.* Nix is retired estate-wide (owner ruling 2026-06-05); - `guix.scm` / guix channels are the sole reproducible-build manifests. No - `flake.nix` remains; all references reconciled. +* *Guix-only builds.* Nix is retired estate-wide (owner ruling 2026-06-05). + No `flake.nix` remains; all references reconciled. Guix is the only + reproducible-build manifest the estate accepts, but this repository does + not track a `guix.scm` yet; `mise.toml` and `deploy/Containerfile` stand in + until it does. * *The formal-proof corpus checks in CI* across Idris 2 (v0.7.0), Agda (2.8.0.2, one artefact) and TLA+ (bounded TLC model checking, which is `tested-only`, not a proof). What each artefact diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 1fba00e8..bb1dc8dc 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,31 @@ https://semver.org/[Semantic Versioning]. === Changed +==== README claims match the tree, and a gate keeps them there (2026-11-09) + +* Corrected counts in the README: +** rule modules 35 → 42; +** `lib/` files 133 → 154; +** Just recipes 39 → 45; +** CLI commands five → six (`secret-alerts`); +** Mix tasks "eleven more" → twelve. +* `just build-all` is described as what it builds, the Rust workspace and + Elixir, with `build-ffi` and `compile-abi` listed beside it. +* The `guix shell -m guix.scm` step is gone: no `guix.scm` is tracked. The + README and AFFIRMATION say so, and name `mise.toml` and + `deploy/Containerfile` as the stand-ins. +* The rule-distillation loop is presented as the design, not the running code. +* The `/api/*` access policy is stated as `api_router.ex` applies it. +* Dead links fixed: +** `topology.md` and `DEBT-REGISTER.md` (both `.adoc`), and + `CONTRIBUTING.md` (in `.github/`); +** 17 `.md` → `.adoc` links in `docs/README.adoc`. +* `scripts/check-readme-claims.sh` measures the counts the README quotes, + and resolves every local link in the README, EXPLAINME and the docs index. + It runs in the Validate Documentation job and fails on drift, where the + older link step only warned. `tests/readme-claims-test.sh` plants each kind + of drift. + ==== Proof-ledger statuses are derived from CI receipts (2026-11-09) * Every ledger row names its `lane`: the CI job whose receipt decides it diff --git a/EXPLAINME.adoc b/EXPLAINME.adoc index c473c572..b2dc39da 100644 --- a/EXPLAINME.adoc +++ b/EXPLAINME.adoc @@ -17,28 +17,29 @@ or `mix hypatia.watch`. [quote, README.adoc] ____ -*Symbolic reasoning* — 35 Elixir rule modules producing fast, deterministic findings +*Symbolic reasoning* — 42 Elixir rule modules producing fast, deterministic findings ____ [quote, README.adoc] ____ -The system learns complex patterns through neural analysis, then *distils* them into simple, -fast declarative rules that can be pre-emptively injected into new repositories, applied as -cures to existing ones, and executed without ML inference overhead. +That loop is the design, not yet the running code. Today the rules are hand-written, and the +neural layer can only lower a recipe's confidence or demote a finding. ____ How this is implemented:: Pure-Elixir modules in link:lib/rules/[] behind `Hypatia.Rules` (link:lib/rules/rules.ex[]). The escript runs them with no OTP tree and no -neural process (`app: nil` in link:mix.exs[]). +neural process (`app: nil` in link:mix.exs[]). The count is +`find lib/rules -maxdepth 1 -name '*.ex' | wc -l`. link:scripts/check-readme-claims.sh[] +measures it, with the README's other structural counts, in the Validate Documentation +job, and fails on drift. Caveat:: -`find lib/rules -maxdepth 1 -name '*.ex' | wc -l` gives 42, not 35; the -generated link:docs/rules/catalogue.adoc[] says 41 and lacks +The generated link:docs/rules/catalogue.adoc[] says 41 and lacks `structured_format`. No code path distils learned patterns into rules: `learn_from_fix/3` and `promote_patterns/0` in link:lib/rules/learning.ex[] have no outside callers, and `git grep RecipeGenerator` finds no caller. -The rules are hand-written. +The README states this beside the design sentence. === Neural learning @@ -140,7 +141,7 @@ an action is capped at review, not report-only (line 67). [quote, README.adoc] ____ -133 Elixir modules — pipeline, rules, VCL, neural, safety, web, merge orchestration +154 Elixir source files — pipeline, rules, VCL, neural, safety, web, merge orchestration ____ How this is implemented:: @@ -149,8 +150,8 @@ Each README row, measured: [cols="1,2", options="header"] |=== | README row | Tree -| `lib/`: 133 | 154 `.ex` files (`+ls lib/**/*.ex \| wc -l+`), 180 `defmodule` -| `lib/rules/`: 35 | 42, as above +| `lib/`: 154 | 154 `.ex` files (`+find lib -name '*.ex' \| wc -l+`), 180 `defmodule`; gated +| `lib/rules/`: 42 | 42, as above; gated | `lib/neural/`, `lib/vcl/` | Correct | `lib/merge_orchestration/`: 12 | Correct (one file in `kin_gate/`) | `lib/hypatia/web/` | SARIF is link:lib/hypatia/sarif.ex[], outside `web/` @@ -158,43 +159,50 @@ Each README row, measured: | `ffi/zig/`: 18, 16 | Correct; pinned by link:.github/workflows/zig.yml[] | Rust workspace | 7 members in link:Cargo.toml[] |=== ++ +"Gated" means link:scripts/check-readme-claims.sh[] measures the row in CI and +fails when the README disagrees. The same check fails on a dead local link in +the README, this file or link:docs/README.adoc[]. Caveat:: -The README links `docs/architecture/topology.md`; the file is -link:docs/architecture/topology.adoc[]. +The other rows are measured by hand, as of the date above. === Installation, usage and the operational surface [quote, README.adoc] ____ -`just --list` shows all 39 recipes; `CONTRIBUTING.md` treats `just` as the canonical entry point. +`just --list` shows all 45 recipes; link:.github/CONTRIBUTING.md[`CONTRIBUTING.md`] treats `just` as the canonical entry point. ____ [quote, README.adoc] ____ Running the supervision tree also serves an HTML dashboard, an SSE event stream, a Prometheus -`/metrics` endpoint and a bearer-authenticated `/api/*` on port 9090. +`/metrics` endpoint and an `/api/*` on port 9090. With +`HYPATIA_API_BEARER_TOKEN` set, every `/api/*` request needs that bearer token. Without it, +only loopback clients are answered unless `HYPATIA_API_ALLOW_NONLOCAL=true`. ____ How this is implemented:: link:Justfile[] (with `contractile.just`), link:lib/hypatia/cli.ex[], link:lib/mix/tasks/[]; link:mise.toml[] pins Erlang 27.3.4.4 and Elixir 1.17.3. link:lib/application.ex[] supervises 24 children (two more when -configured); link:lib/hypatia/web/router.ex[] serves `/`, `/metrics`, `/api`. +configured); link:lib/hypatia/web/router.ex[] serves `/`, `/metrics`, `/api`, +and `protect/2` in link:lib/hypatia/web/api_router.ex[] applies the bearer +and loopback policy. The recipe count is `just --summary | wc -w`; the six +CLI commands and the sixteen Mix tasks the README describes are gated as well. Caveat:: -The Justfiles hold 43 recipes. `just build-all` builds Rust and Elixir only. -The CLI has six commands, not five (`secret-alerts`); there are 16 Mix -tasks, not 15. No `guix.scm` is tracked. `CONTRIBUTING.md` is in `.github/`. -`/api/*` needs a bearer token only when `HYPATIA_API_BEARER_TOKEN` is set; -otherwise it is loopback-only. +`just build-all` builds the Rust workspace and Elixir; the README lists +`just build-ffi` and `just compile-abi` beside it for the Zig FFI and the +Idris2 ABI. No `guix.scm` is tracked: the README and AFFIRMATION say so, and +name `mise.toml` and `deploy/Containerfile` as the stand-ins. === Project status and licence [quote, README.adoc] ____ -242 tests that are excluded from every run, the safety systems that are built but not yet invoked -by any dispatch path, and the CI blockage currently stopping the Rust lane. +That includes the `:verisim_data` tests excluded from every run and the safety systems that are +built but not yet invoked by any dispatch path. ____ [quote, README.adoc] @@ -209,12 +217,11 @@ that tag. The safety modules are in link:lib/safety/[]. Every tracked `+lib/**/*.ex+` file carries `MPL-2.0`; link:LICENSES/[] holds both texts. Caveat:: -The nine files hold 224 literal `test` blocks; 242 needs a `mix test` run -this audit did not make. The Rust blockage is stale: entry CI-1 of -link:docs/DEBT-REGISTER.adoc[] records the fix, visible in -link:integration/Cargo.toml[] lines 48–55. `docs/DEBT-REGISTER.md` is dead. -45 of 152 tracked `.adoc` files have no SPDX header (recorded for the owner, -not a licence change). +The nine files hold 224 literal `test` blocks; the README no longer quotes +a count. Entry CI-1 of link:docs/DEBT-REGISTER.adoc[] records the fix for +the former Rust-lane blockage, visible in link:integration/Cargo.toml[] +lines 48–55. 45 of 152 tracked `.adoc` files have no SPDX header (recorded +for the owner, not a licence change). == Dogfooded across the account @@ -250,12 +257,10 @@ have no callers, so its circuit breaker never opens. The moduledoc of [CAUTION] ==== -README overclaims, each detailed above: learned patterns are not distilled -into rules (the promotion and recipe-generation code is never invoked); the -counts of rule modules, `lib/` modules, Just recipes, CLI commands and Mix -tasks are wrong; `just build-all` is narrower than stated; `guix.scm` is -absent; two links are dead. Excluded `:verisim_data` tests run in no -workflow (debt register T-1). +Learned patterns are not distilled into rules: the promotion and +recipe-generation code is never invoked. The README now presents the loop as +the design and says the rules are hand-written. No `guix.scm` is tracked. +Excluded `:verisim_data` tests run in no workflow (debt register T-1). ==== == Evidence index diff --git a/README.adoc b/README.adoc index 8647158e..ac1897a2 100644 --- a/README.adoc +++ b/README.adoc @@ -19,7 +19,7 @@ toc::[] *Hypatia* is the CI/CD governance scanner for the hyperpolymath estate (~400 repositories). It combines: -* *Symbolic reasoning* — 35 Elixir rule modules producing fast, deterministic findings +* *Symbolic reasoning* — 42 Elixir rule modules producing fast, deterministic findings (link:docs/rules/catalogue.adoc[full rule catalogue]) * *Neural learning* — 8 networks on a shared blackboard, learning which fixes actually stick * *Bayesian confidence* — every recipe carries a Beta-distributed confidence updated from real @@ -40,6 +40,10 @@ The system learns complex patterns through neural analysis, then *distils* them fast declarative rules that can be pre-emptively injected into new repositories, applied as cures to existing ones, and executed without ML inference overhead. +That loop is the design, not yet the running code. Today the rules are hand-written, and the +neural layer can only lower a recipe's confidence or demote a finding. The promotion code in +`lib/rules/learning.ex` has no caller (link:EXPLAINME.adoc[EXPLAINME]). + == The safety triangle Every finding is routed *Eliminate > Substitute > Control*, in that order — remove the hazard @@ -71,8 +75,8 @@ PatternRegistry ──> TriangleRouter ──> FleetDispatcher ──> DispatchM [cols="1,3"] |=== -| `lib/` | 133 Elixir modules — pipeline, rules, VCL, neural, safety, web, merge orchestration -| `lib/rules/` | 35 rule modules; see link:docs/rules/catalogue.adoc[the catalogue] +| `lib/` | 154 Elixir source files — pipeline, rules, VCL, neural, safety, web, merge orchestration +| `lib/rules/` | 42 rule modules; see link:docs/rules/catalogue.adoc[the catalogue] | `lib/neural/` | 8 networks + blackboard, coordinator, persistence, rebalancer | `lib/vcl/` | VCL query layer over verisim-data: parser, file executor, cache, federation | `lib/merge_orchestration/` | 12 modules — leases, ledger, ticker @@ -83,24 +87,27 @@ PatternRegistry ──> TriangleRouter ──> FleetDispatcher ──> DispatchM | `verification/` | Proof ledger and proofs: Idris2, Agda, TLA+ |=== -See link:docs/architecture/topology.md[docs/architecture/topology.md] for the full map. +See link:docs/architecture/topology.adoc[docs/architecture/topology.adoc] for the full map. == Installation -Guix is the reproducible-build path; Nix was retired estate-wide on 2026-06-05. Podman, not -Docker, is the container tool. +Guix is the estate's reproducible-build path; Nix was retired estate-wide on 2026-06-05. +This repository does not track a `guix.scm` yet. Until it does, `mise.toml` pins the +toolchain and `deploy/Containerfile` builds the image. Podman, not Docker, is the +container tool. [source,bash] ---- git clone https://github.com/hyperpolymath/hypatia.git cd hypatia -guix shell -m guix.scm # reproducible environment (optional) just doctor # toolchain preflight -just build-all # Elixir + Rust + Zig FFI + Idris2 ABI +just build-all # Rust workspace + Elixir +just build-ffi # Zig FFI +just compile-abi # Idris2 ABI ---- -`just --list` shows all 39 recipes; `CONTRIBUTING.md` treats `just` as the canonical entry point. +`just --list` shows all 45 recipes; link:.github/CONTRIBUTING.md[`CONTRIBUTING.md`] treats `just` as the canonical entry point. === Elixir development @@ -125,23 +132,26 @@ or clean test run. == Usage -The escript exposes five commands: +The escript exposes six commands: [source,bash] ---- hypatia scan [--format json|text|github|sarif] [--exit-zero] [--advisory] hypatia report hypatia pr-eligibility --owner --repo --pr +hypatia secret-alerts --owner --repo hypatia version hypatia help ---- Mix tasks cover the operational surface — `mix hypatia.rsr_score`, `mix hypatia.recipe_health`, -`mix hypatia.watch`, `mix hypatia.triage_issues` and eleven more. See the +`mix hypatia.watch`, `mix hypatia.triage_issues` and twelve more. See the https://github.com/hyperpolymath/hypatia/wiki/Operations[Operations wiki page]. Running the supervision tree also serves an HTML dashboard, an SSE event stream, a Prometheus -`/metrics` endpoint and a bearer-authenticated `/api/*` on port 9090. +`/metrics` endpoint and an `/api/*` on port 9090. With +`HYPATIA_API_BEARER_TOKEN` set, every `/api/*` request needs that bearer token. Without it, +only loopback clients are answered unless `HYPATIA_API_ALLOW_NONLOCAL=true`. === Consuming the RSR oracle from another repository @@ -177,10 +187,10 @@ Development state, milestones and known gaps live in machine-readable form: * `.machine_readable/descriptiles/PLAYBOOK.a2ml` — operational runbook * link:ROADMAP.adoc[ROADMAP.adoc] — milestone history -**Before trusting any metric or green board, read link:docs/DEBT-REGISTER.md[docs/DEBT-REGISTER.md].** -It is the authoritative, evidence-backed account of what is broken, ungated or stale — including -242 tests that are excluded from every run, the safety systems that are built but not yet invoked -by any dispatch path, and the CI blockage currently stopping the Rust lane. +**Before trusting any metric or green board, read link:docs/DEBT-REGISTER.adoc[docs/DEBT-REGISTER.adoc].** +It is the authoritative, evidence-backed account of what is broken, ungated or stale. That +includes the `:verisim_data` tests excluded from every run and the safety systems that are +built but not yet invoked by any dispatch path. == Documentation diff --git a/docs/README.adoc b/docs/README.adoc index 68badb03..32618aee 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -17,8 +17,8 @@ Pick one based on who you are. Each is a 1-page on-ramp. * link:quickstart/user.adoc[`user.adoc`] — install + run a scan. * link:quickstart/developer.adoc[`developer.adoc`] — clone + extend a rule. * link:quickstart/maintainer.adoc[`maintainer.adoc`] — deploy + monitor. -* link:quickstart/llm-warmup-user.md[`llm-warmup-user.md`] — short context dump for chat-LLM users. -* link:quickstart/llm-warmup-dev.md[`llm-warmup-dev.md`] — context dump for coding-agent users. +* link:quickstart/llm-warmup-user.adoc[`llm-warmup-user.adoc`] — short context dump for chat-LLM users. +* link:quickstart/llm-warmup-dev.adoc[`llm-warmup-dev.adoc`] — context dump for coding-agent users. == Guides @@ -33,18 +33,18 @@ Pick one based on who you are. Each is a 1-page on-ramp. == Architecture -* link:architecture/README.md[`README.md`] — architecture overview. -* link:architecture/NEURAL-ARCHITECTURE.md[`NEURAL-ARCHITECTURE.md`] — 8 neural networks, 6-phase blackboard. +* link:architecture/README.adoc[`README.adoc`] — architecture overview. +* link:architecture/NEURAL-ARCHITECTURE.adoc[`NEURAL-ARCHITECTURE.adoc`] — 8 neural networks, 6-phase blackboard. * link:architecture/togaf-overview.adoc[`togaf-overview.adoc`] — TOGAF baseline, ADR-001..006. * link:architecture/mof-metamodel.adoc[`mof-metamodel.adoc`] — OMG MOF M2 metamodel. * link:architecture/boundary-design-options.adoc[`boundary-design-options.adoc`] — VCL / SNIF / verisimdb boundary (issue #294 decision memo). -* link:architecture/topology.md[`topology.md`] — ecosystem topology. -* link:architecture/system-integration.md[`system-integration.md`] — how Hypatia integrates with the wider estate. +* link:architecture/topology.adoc[`topology.adoc`] — ecosystem topology. +* link:architecture/system-integration.adoc[`system-integration.adoc`] — how Hypatia integrates with the wider estate. == Design -* link:design/README.md[`README.md`] — design index. -* link:design/DESIGN-NARRATIVE.md[`DESIGN-NARRATIVE.md`] — narrative design rationale. +* link:design/README.adoc[`README.adoc`] — design index. +* link:design/DESIGN-NARRATIVE.adoc[`DESIGN-NARRATIVE.adoc`] — narrative design rationale. * link:design/design-decisions.adoc[`design-decisions.adoc`] — atomic decision log. == Operations — runbooks + design memos @@ -69,7 +69,7 @@ acceptance criteria for closing the underlying roadmap item. * link:integration/a2ml.adoc[`a2ml.adoc`] — A2ML machine-readable metadata integration. * link:integration/k9.adoc[`k9.adoc`] — K9 coordination service integration. -* link:integration/a2ml-k9.md[`a2ml-k9.md`] — combined integration notes. +* link:integration/a2ml-k9.adoc[`a2ml-k9.adoc`] — combined integration notes. * link:integration/github-registry.adoc[`github-registry.adoc`] — GitHub registry integration. == Security @@ -97,14 +97,14 @@ Detailed per-surface references. For the high-level overview see link:guides/api == Testing + proofs -* link:testing/needs.md[`testing/needs.md`] — coverage targets. -* link:testing/audit.md[`testing/audit.md`] — test-suite audit. -* link:proofs/needs.md[`proofs/needs.md`] — Idris2 proof obligations. -* link:proofs/gap-analysis.md[`proofs/gap-analysis.md`] — proof-coverage gap analysis. +* link:testing/needs.adoc[`testing/needs.adoc`] — coverage targets. +* link:testing/audit.adoc[`testing/audit.adoc`] — test-suite audit. +* link:proofs/needs.adoc[`proofs/needs.adoc`] — Idris2 proof obligations. +* link:proofs/gap-analysis.adoc[`proofs/gap-analysis.adoc`] — proof-coverage gap analysis. == Readiness + reports -* link:readiness.md[`readiness.md`] — CRG declaration + component grades. +* link:readiness.adoc[`readiness.adoc`] — CRG declaration + component grades. * link:reports/audit/[`reports/audit/`] — dated audit reports. * link:SESSION-LOG.adoc[`SESSION-LOG.adoc`] — long-running session record. @@ -120,10 +120,10 @@ Detailed per-surface references. For the high-level overview see link:guides/api == Diagrams -* link:diagrams/architecture.md[`architecture.md`] -* link:diagrams/bot-orchestration.md[`bot-orchestration.md`] -* link:diagrams/data-flow.md[`data-flow.md`] -* link:diagrams/rule-execution.md[`rule-execution.md`] +* link:diagrams/architecture.adoc[`architecture.adoc`] +* link:diagrams/bot-orchestration.adoc[`bot-orchestration.adoc`] +* link:diagrams/data-flow.adoc[`data-flow.adoc`] +* link:diagrams/rule-execution.adoc[`rule-execution.adoc`] == Wiki page sources diff --git a/scripts/check-readme-claims.sh b/scripts/check-readme-claims.sh new file mode 100755 index 00000000..c509df97 --- /dev/null +++ b/scripts/check-readme-claims.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 # literal backticks (AsciiDoc code spans) and inner-sh $1/$2, not expansions +# SPDX-License-Identifier: MPL-2.0 +# The structural counts the README quotes, measured from the tree, and every +# local link: target in the README, EXPLAINME and the docs index. A count or a +# link that no longer matches fails the check, so the README cannot drift the +# way it did (35 rule modules quoted against 42 on disk). +# +# Usage: check-readme-claims.sh # check, exit 1 on any mismatch +# README_CLAIMS_ROOT=DIR ... # check another checkout (tests) +set -euo pipefail +root=${README_CLAIMS_ROOT:-$(cd "$(dirname "$0")/.." && pwd)} +cd "$root" +readme=README.adoc +fail=0 +say() { echo "::error::readme-claims: $*"; fail=1; } + +words=(zero one two three four five six seven eight nine ten eleven twelve + thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty) + +# The Just recipes `just --summary` lists: every public recipe in the Justfile +# and the files it imports. Without just, the same rule as a grep. +recipes() { + if command -v just >/dev/null 2>&1; then + just --summary | wc -w + else + local files=(Justfile) + while IFS= read -r f; do [ -f "$f" ] && files+=("$f"); done \ + < <(sed -nE 's/^import\??[[:space:]]+"([^"]+)".*/\1/p' Justfile) + grep -hE '^@?[A-Za-z][A-Za-z0-9_-]*( [^:]*)?:( |$)' "${files[@]}" | grep -vc ':=' + fi +} + +# expect +expect() { + local what=$1 n=$2 pat=${3//N/$2} + grep -qF -- "$pat" "$readme" || say "$what is $n in the tree; $readme should say: $pat" +} + +rules=$(find lib/rules -maxdepth 1 -name '*.ex' | wc -l) +expect "lib/rules" "$rules" "*Symbolic reasoning* — N Elixir rule modules" +expect "lib/rules" "$rules" '| `lib/rules/` | N rule modules' +expect "lib/" "$(find lib -name '*.ex' | wc -l)" '| `lib/` | N Elixir source files' +expect "Just recipes" "$(recipes)" '`just --list` shows all N recipes' + +tasks=$(find lib/mix/tasks -maxdepth 1 -name 'hypatia.*.ex' | wc -l) +named=$(grep -A2 '^Mix tasks cover the operational surface' "$readme" | grep -oE '`mix hypatia\.[a-z_]+`' | sort -u | wc -l) +more=$((tasks - named)) +if [ "$more" -ge 0 ] && [ "$more" -lt ${#words[@]} ]; then + expect "Mix tasks ($tasks, $named named)" "${words[$more]}" "and N more." +else + say "Mix tasks: $tasks in the tree, $named named in $readme" +fi + +# The escript's top-level commands: the case arms of Hypatia.CLI.main/1. +commands=$(grep -cE '^[[:space:]]+\["[a-z-]+"( \| [a-z_]+)?\] ->' lib/hypatia/cli.ex) +if [ "$commands" -lt ${#words[@]} ]; then + expect "CLI commands" "${words[$commands]}" "The escript exposes N commands:" +else + say "CLI commands: $commands" +fi + +# Every local link: target resolves, relative to the document that names it. +for doc in "$readme" EXPLAINME.adoc docs/README.adoc; do + [ -f "$doc" ] || continue + dir=$(dirname "$doc") + while IFS= read -r target; do + case "$target" in http://*|https://*|mailto:*|"") continue ;; esac + path=${target%%#*} + [ -z "$path" ] && continue + [ -e "$dir/$path" ] || say "$doc links $target, which does not exist" + done < <(grep -oE 'link:[^[ ]+\[' "$doc" | sed -E 's/^link:(.*)\[$/\1/') +done + +[ "$fail" -eq 0 ] && echo "readme-claims: counts and local links match the tree" +exit "$fail" diff --git a/tests/readme-claims-test.sh b/tests/readme-claims-test.sh new file mode 100755 index 00000000..dec0eb46 --- /dev/null +++ b/tests/readme-claims-test.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 # literal backticks (AsciiDoc code spans) and inner-sh $1/$2, not expansions +# SPDX-License-Identifier: MPL-2.0 +# Contracts of scripts/check-readme-claims.sh: the real tree passes, and each +# planted drift (a stale count, a dead link) turns it red. The recipe count +# must agree with and without `just` on PATH. +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" +work="$(mktemp -d)" +trap 'rm -rf -- "$work"' EXIT +script="$repo_root/scripts/check-readme-claims.sh" +pass=0; fail=0 + +expect_msg() { #