Skip to content

ci: check the docs in CI and before every push - #9

Merged
LukiPrince merged 5 commits into
mainfrom
ci/docs-check
Nov 7, 2026
Merged

LukiPrince merged 5 commits into
mainfrom
ci/docs-check

Conversation

@LukiPrince

Copy link
Copy Markdown
Owner

Checks that the docs follow the code, in CI and before every push.

tools/docs/check_docs.py (stdlib only) reads the Markdown files tracked by git at a revision and reports:

  • relative links and #anchors that do not resolve to a file in git (anchors computed like GitHub's heading slugs; all 137 existing anchors were compared with github.com);
  • setup file names that are not in web/lib/setup-downloads.json;
  • "open source" without "not"/"never" in the same paragraph or list item (the project is source available).

It found one real problem: SECURITY.md linked ../../security/advisories/new, which only works on github.com; now an absolute link.

tools/githooks/pre-push (turn on once per clone with git config core.hooksPath tools/githooks): git calls it at the real push (githooks) and it checks the tip commit of each pushed ref with check_docs. In Claude Code (CLAUDECODE=1) it also holds the push until the docs were compared with the change and python tools/docs/push_hook.py --mark was run. Already-pushed commits are skipped, and every internal error fails open: only the hook's own exit code 3 aborts a push.

A Claude Code PreToolUse hook was the first version; three adversarial review rounds kept finding pushes that command-text parsing missed or commands it wrongly blocked, so it was replaced by git's own hook, which needs no parsing.

CI: a new docs job runs the tests (102) and the check. It becomes a required check in the main ruleset once this run has shown its name.

Tested end to end: this branch's own push was held by the hook (not marked yet), then accepted after --mark.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8

LukiPrince and others added 5 commits November 7, 2026 18:36
tools/docs/check_docs.py checks the tracked Markdown files: relative links and
#anchors resolve (GitHub slugs), no links to maintainer-local files, setup file
names match web/lib/setup-downloads.json, "open source" only next to not/never.
tools/docs/push_hook.py is a PreToolUse hook (.claude/settings.json) that holds
Claude's git push until the check is clean and the docs were compared with the
push (marker in .git, set with --mark). New CI job "docs". SECURITY.md: the
advisory link is now absolute, the relative form only worked on github.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8
- hook command starts the script only if it exists (python exits 2 on a missing file and
  would block every Bash/PowerShell call); onFailure: continue for Windows without Git Bash
- is_push tokenizes the command (shlex) instead of searching the raw string; a push chained
  with another git command is blocked, the stale-marker message says to run --mark alone
- the docs check reads the file list and contents at HEAD (git ls-tree / git show), so a
  link to a gitignored file is a finding locally as in CI; the maintainer-local rule is gone
- the hook acts only in a repo that has tools/docs/push_hook.py
- code spans are blanked per paragraph (they cross line breaks)
- slug(): html.unescape, Nd/Nl/Pc only, paired emphasis, github-slugger duplicate loop
- AGENTS.md and ci.yml name the same coverage; docstrings no longer claim exit 1 always passes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8
Guessing "is this a git push?" from Bash/PowerShell text kept missing cases. git's own
pre-push hook (https://git-scm.com/docs/githooks#_pre_push) gets the pushed commits from git
itself: tools/githooks/pre-push runs check_docs on every pushed commit and, in Claude Code
(CLAUDECODE set), holds the push until `python tools/docs/push_hook.py --mark`.

- remove .claude/settings.json and the command parser of push_hook.py
- push_hook.py is the pre-push implementation (pure decide(), IO in main(), fails open)
- .gitattributes: tools/githooks/* is LF, a CRLF sh script breaks
- check_docs: the "open source" wording rule works per paragraph, the docs are hard-wrapped
- tests with real git: a bare remote and a clone with the hook on

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8
- wrapper probes python and python3 for a working 3.10+ (the Microsoft Store alias answers
  command -v and exits 49), still fails open
- parse the stdin lines with rsplit (a local ref like HEAD^{/some words} holds spaces)
- import check_docs inside the fail-open try; the marker under CLAUDECODE no longer depends on it
- only the tip of each pushed ref is checked, and a tip already on a remote-tracking ref is skipped
- the hint range uses the remote being pushed to; messages name the checked ref and short sha
- the marker lives in the git common dir, shared by all worktrees
- wording rule cuts blocks at list items; an invalid setup-downloads.json is a finding
- drop the redundant .gitattributes rule (* text=auto eol=lf already gives the hook LF)
- AGENTS.md, CONTRIBUTING.md, ci.yml: say what is checked and that core.hooksPath turns it on

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8
python exits 1 on a SyntaxError, so a broken push_hook.py used to block every push. The hook now
aborts with 3 and the wrapper lets every other code through. Also rewraps the SECURITY.md line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEvvb1DM3ZA5FqELYSs4v8
@LukiPrince
LukiPrince merged commit 6315a10 into main Nov 7, 2026
28 checks passed
@LukiPrince
LukiPrince deleted the ci/docs-check branch November 7, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant