#!/usr/bin/env python3 """PATH AUDIT — no document may name a file or directory that does not exist. Why this exists --------------- The docs used to name directories, modules and tests that were never created. A reader who acts on a documented path loses trust in the whole document, and nobody can tell "we decided against this" from "this is wrong". So the rule is: **every path-shaped token written in backticks in a document must resolve**, including in the retired/deprecated registry (`docs/retired.md` is scanned too) — there, a fictional path reads the same as a real one. The invented names are deliberately not repeated here, so the examples cannot be copied back in; `git log -p docs/retired.md` shows what they were. What this check proves ---------------------- Every path-shaped token in inline code in the scanned documents is one of: (a) resolvable — it exists in this repo (see ROOT_DIRS, a token is resolved against several real roots because docs cite files as `config.rs`, not always `src/config.rs`); (b) attributed to an external repo by an owning prefix (EXEMPT_EXTERNAL), e.g. `stalwartlabs/jmap-client/src/client.rs`; (c) provably deleted — the file is gone but `git log --all --diff-filter=A` shows it was committed at some point (e.g. `docs/todo.md`); (d) on the commented CONVENTIONAL allowlist — a well-known filename that the prose explicitly says this repo does not have. Anything else is reported as an error. What it does NOT prove ---------------------- - That the path means what the prose says, same limitation as audit_anchors. - Anything outside inline code. Backticks are the contract: if you write a path in prose without backticks, this check cannot see it. - Bare filenames unambiguously: `client.rs` resolves against any file with that basename in the repo. - Anything in scripts. Code legitimately contains path-like string literals (this check is defined by string patterns, after all); those are not documentation claims. Non-path shapes that look like paths are excluded by two structural rules rather than a denylist: - A `seg/` token only counts as a directory reference when it IS the whole inline-code span. TOKEN happily returns the `Email/` prefix of `Email/changes`, the `application/` of `application/json` and the `api.telegram.org/` of `api.telegram.org/bot`; none of those are the whole span, so they are dropped here. This is what keeps JMAP methods, media types, dotted hosts, `{jmap_origin}/.well-known/jmap` templates and `method/path/origin/失败类别` log formats out. Absolute paths are excluded one step earlier by TOKEN's lookbehind, which refuses to start a match after a '/', so `/api/status` never reaches the scanner at all. - is_noise() then catches the remainder that survives that test: dot-prefixed URL parts (`.well-known/jmap`), and, for whole-span `seg/` tokens, media types, dotted hosts, all-numeric pairs and shell/glob fragments. That last list is fallback, not the main defence — if you find yourself wanting to add a case there, suspect the document instead. """ import os import re import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(HERE, "..", "..")) DOCS = [ os.path.join(ROOT, "docs", f) for f in sorted(os.listdir(os.path.join(ROOT, "docs"))) if f.endswith(".md") ] AGENTS = os.path.join(ROOT, "AGENTS.md") TARGETS = [t for t in DOCS + [AGENTS] if os.path.isfile(t)] # Directories a doc legitimately roots a path at. Every entry is real. ROOT_DIRS = [ ".", "src", "src/domain", "src/domain/jmap", "docs", "scripts", "scripts/docs_check", "cloudflare-worker", "cloudflare-worker/src", "web", ] EXTS = "rs|js|mjs|ts|tsx|json|toml|ya?ml|md|sh|sql|cfg|lock|html|css|proto|txt" # A file token (ends in a known extension) or a directory token (ends in '/'). # ',' is in the segment class so a brace glob like `{mod,commands}.rs` is kept # whole and expanded by variants(); drop it and the glob is mangled into two # bogus tokens, one of which does not resolve and reports the wrong filename. TOKEN = re.compile( r"(? {v}" errors.append(f"{rel}:{i} `{shown}` does not resolve " f"(ROOT_DIRS, git history, or CONVENTIONAL)") break print(f"=== PATH AUDIT ({len(errors)} errors, " f"{checked} path-refs checked) ===") for e in errors: print(" " + e) return 1 if errors else 0 if __name__ == "__main__": sys.exit(main())