ci: add a deterministic docs linter, replacing the redirect checker - #211
Merged
Merged
Conversation
Checks the things that were wrong across the docs and can be verified without judgment: - relative links and `#anchors` resolve - ```json samples parse - a denylist of misspellings we have actually had - product-name casing in prose (WebSocket, Docker Compose, Helm, GitHub, OpenAPI), skipping code spans, URLs and identifiers - headings are title case, except question-style ones - table rows share a width and a column count - no trailing whitespace, keeping real Markdown hard breaks - every `.gitbook.yml` redirect target resolves - a moved or deleted page has a redirect It reports only findings a change introduces, by running every check against two trees and diffing. Two consequences: - Existing debt never fails a build, so a stricter convention can be adopted without a repo-wide cleanup first. There are 159 pre-existing findings today, mostly heading case. - Comparing against the **target branch tip** rather than the merge base catches a merge that silently undoes a fix already on main. That is what happened between #207 and #208: #207 changed a heading level on a line #208 had just corrected, from a base that predated it, and git reported no conflict. Finding identity excludes the line number, so moving a paragraph does not resurface everything below it as new. Stdlib only, no dependencies, ~2s for the whole repo. Runnable locally: python3 .github/scripts/docs_lint.py --base origin/main
The linter needs the target branch to diff against, hence `fetch-depth: 0` plus an explicit fetch. actions/checkout gives us the merge of the PR into the target branch, which is what makes the merge-regression case detectable. Removes redirect-url-checker.yml. It probed the published site over the network, which GitBook rate-limits, so it never passed reliably - and it was `workflow_dispatch` only, so in practice nothing ran on a pull request at all. Its purpose is covered locally and deterministically by the new redirect-target and moved-file checks, which read `.gitbook.yml` and the tree instead of making requests.
The new redirect-target check found 38 redirects aimed at files that no
longer exist, so those old URLs 404 today. They are all fallout from the
reorganisation that introduced `concepts/` and `quick-start/`; only six
distinct targets are involved:
use-steadybit/actions.md -> concepts/actions/README.md (25)
use-steadybit/discovery/README.md -> concepts/discovery/README.md (6)
getting-started.md -> quick-start/getting-started.md (3)
troubleshooting/agent.md -> troubleshooting/common-fixes/agents.md (2)
use-steadybit/experiments/file-import-export -> .../experiments/share/file-import-export (1)
integrate-with-steadybit/webhooks.md -> integrate-with-steadybit/webhooks/README.md (1)
Every destination verified to exist. No redirect keys change, so no
currently-working URL is affected.
One place for the rules, so the linter, contributors and any future review agent agree. Records the decisions that were open: - Headings are title case, except question-style headings, which stay in sentence case - the troubleshooting pages are written as questions and title-casing them reads wrong. - US English: color, behavior, organization, analyze, canceled. This was already true throughout, but was never written down, so nothing stopped it drifting back. - Lists use the serial (Oxford) comma. Noted as *not* linted: separating a three-item list from a compound such as "open- and closed-source extensions" is not reliably detectable, and every sample the draft check flagged was a false positive. Also notes the scope difference the linter applies - British forms are prose-only, plain misspellings apply everywhere including code samples - and updates the CI/CD section, which still described the removed redirect checker.
Review question: the table looked like it mixed wrong spellings with right ones. It is a `written -> should say` mapping, so "Kubernetes" is the suggestion for "kuberneters" rather than an entry - but 23 pairs packed onto continuation lines made that genuinely hard to see. One entry per line now, aligned and grouped by kind. Two real problems that question surfaced: - Lookups were done on a lowercased line, so keys had to be lowercase or they would silently never match, and nothing said so. Matching is now explicitly case-insensitive, and an assertion rejects a capitalised key instead of letting it quietly do nothing. - A finding quoted the table key rather than the file's own text, so a heading reading "Langauge" produced `"langauge" should be ...`. It now quotes what is actually written. Also corrects the suggestion for "langauge" to the common noun "language"; it only looked capitalised because the single instance we had sat inside a title.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
docs-public had no CI on pull requests at all — the only workflow was
workflow_dispatchonly and probed the published site. Every one of the ~45 correctness defects found in the recent read-through (#208, #209, #210) could have been caught before merge, and nothing was looking.This adds a deterministic linter, replaces the flaky redirect checker, and writes the conventions down.
Only what the change introduces
Every check runs against two trees and the results are diffed, so a pull request is judged on what it adds. Two consequences:
Finding identity deliberately excludes the line number, so moving a paragraph doesn't resurface everything below it as new.
The baseline is the target branch tip, not the merge base
This is the one design decision worth pausing on.
actions/checkoutgives us the PR merged intomain, and we compare that againstmainitself — not against the merge base.That is the only way to catch what happened between #207 and #208: #207 changed a heading's level on a line #208 had just corrected, from a base that predated it. Git reported no conflict, the typo came back, and nobody noticed until a checker found it two days later. Against the merge base that regression is invisible; against
mainit is a new finding. Test T2 below is exactly that scenario.Checks
link,anchor#anchorsresolve, under both slug conventions in use herejson```jsonsamples parse (deliberate...elisions skipped)spellingus-englishproduct-nameheading-case?table-width,table-columnstrailing-whitespaceredirect-target.gitbook.ymltarget resolves to a filemoved-fileWhy
spellingandus-englishdiffer in scope:labelled: truecould be a real config key, and renaming someone's field is not a docs decision — whereasKubernetersis never a valid identifier. That split also closes a blind spot: theKuberneterstypos #208 fixed lived in//comments inside query-language code fences, which a prose-only scan would miss.Stdlib only, no dependencies, ~2s for the whole repo. Runnable locally:
Replacing the redirect checker
redirect-url-checker.ymlis removed. It probed the published site over the network, which GitBook rate-limits, so it never passed reliably — and beingworkflow_dispatchonly, in practice it never ran on a pull request. Its purpose is now covered locally and deterministically byredirect-targetandmoved-file, which read.gitbook.ymland the tree instead of making requests.38 live 404s, found by the new check
redirect-targetimmediately found 38 redirects aimed at files that no longer exist — those old URLs 404 today. All fallout from the reorganisation that introducedconcepts/andquick-start/, across only six distinct targets:Every destination verified to exist. No redirect key changes, so no currently-working URL is affected. It's a separate commit if you'd rather handle it apart from the CI change.
Conventions
CLAUDE.mdgains a Writing Conventions section, so the linter, contributors and any future review agent share one source. It records the three decisions that were open:open- and closed-source extensions,begin and run your first experiments, and one that already had the comma). It is documented for humans and any later prose review instead.Verification
mainmain(the #207 case)No conflict with #210 — the two touch disjoint files and can merge in either order.
Two caveats I'd own
heading-caseis the least mechanical check here. If it proves noisy on real pull requests, demote it to a warning rather than letting people learn to ignore a red check.After this and #210, the standing baseline is ~108 findings — almost entirely heading case (77) and table padding (30). None of it blocks anything.