diff --git a/AGENTS.md b/AGENTS.md index 3f4d2da..41cbf0a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,7 +255,9 @@ implementation agents. compiles (as an empty body) under default features and compile-checks under `--all-features`. Verify with `cargo test` (the doctest run) in both feature configurations; the crate-root - assembly example in `src/lib.rs` is the reference pattern. + assembly example in `src/lib.rs` is the reference pattern. The + `scripts/docs-check.sh` docs gate automates the whole rule (see + below) and also fails on any `ignore` block it finds. ## Verification Commands @@ -271,6 +273,7 @@ cargo test --manifest-path fuzz/shared/Cargo.toml # fuzz corpus replay: the s cargo check --target wasm32-unknown-unknown # the default crate stays wasm-clean cargo clippy --target wasm32-unknown-unknown -- -D warnings cargo publish --dry-run --allow-dirty # before a release +./scripts/docs-check.sh # docs gate: rustdoc -D warnings, zero ignored doctests, no untested badges ``` The wasm check is the structural guard for the "default crate is diff --git a/Cargo.toml b/Cargo.toml index 6abb617..285eee8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://git.alk.dev/alkdev/alktty" readme = "README.md" keywords = ["tty", "terminal", "pty", "channels", "alkcall"] categories = ["network-programming", "asynchronous"] -exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/plans/", "docs/sdd_process.md", "fuzz/"] +exclude = [".opencode/", "AGENTS.md", "scripts/", "docs/reviews/", "docs/plans/", "docs/sdd_process.md", "fuzz/"] [workspace] members = ["."] diff --git a/scripts/docs-check.sh b/scripts/docs-check.sh new file mode 100755 index 0000000..8f58c95 --- /dev/null +++ b/scripts/docs-check.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Docs sanity gate: run before a publish (AGENTS.md convention 16). +# Catches what `cargo doc` alone does not: ignore-marked doc examples, +# which rustdoc renders with the untested badge that docs.rs surfaces on +# the crate page (the tooltip "This example is not tested"; the wording +# varies by rustdoc version, so we grep the stable `example-wrap ignore` +# markup class instead). +# +# 1. rustdoc build with warnings as errors: broken intra-doc links, +# broken anchors, and doc lints fail here instead of surfacing on +# docs.rs (docs.rs does not fail on warnings, so the local gate is +# stricter than production). +# 2. doctests in both feature configurations, with the rule that zero +# doctests are ignored (ignored = uncompiled = untested). The lib +# doctest count is expected: 1 compiled (the assembly example), 0 +# ignored. Feature-gated example bodies compile as empty under +# default features and fully under --all-features. +# 3. badge scan over the rendered HTML: no `example-wrap ignore` markup +# and no "example is ... tested" tooltip text anywhere in target/doc. +set -euo pipefail +root="$(git rev-parse --show-toplevel)" +cd "$root" + +echo "== rustdoc build, warnings as errors (default features)" +RUSTDOCFLAGS="-D warnings" cargo doc --no-deps + +ignored_count() { + echo "$1" | grep -o '[0-9]* ignored' | awk '{s += $1} END {print s + 0}' +} + +for cfg in "" "--all-features"; do + label="default features" + [ -n "$cfg" ] && label="--all-features" + echo "== doctests ($label)" + out="$(cargo test --doc $cfg 2>&1)" + echo "$out" | grep -E '^test result' | sed "s/^/ $label: /" + ignored="$(ignored_count "$out")" + if [ "$ignored" != "0" ]; then + echo "FAIL: $ignored ignored doctest(s) ($label); convention 16:" \ + "examples are tested or no_run, never ignore" >&2 + exit 1 + fi +done + +echo "== untested-badge scan over target/doc" +badges="$(grep -rlE 'example-wrap[ "]?ignore|This example is (not |un)?tested' \ + target/doc/alktty/*.html 2>/dev/null || true)" +if [ -n "$badges" ]; then + echo "FAIL: untested-documentation badge(s) in the rendered docs:" >&2 + echo "$badges" | sed 's/^/ /' >&2 + exit 1 +fi + +echo "docs-check: ok" \ No newline at end of file