Skip to content

Commit 2e18e6d

Browse files
cuipinghuoclaude
andcommitted
Add lightweight AsciiDoc validation for agent and CI use
Adds a `make validate-docs` target that validates documentation without requiring external repositories or network access. This enables agents and CI to catch broken xrefs, missing includes, and AsciiDoc syntax errors before human review. Closes #240 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8ac1af8 commit 2e18e6d

4 files changed

Lines changed: 208 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
name: Validate Docs
3+
4+
"on":
5+
pull_request:
6+
branches:
7+
- main
8+
paths:
9+
- "modules/**"
10+
- "antora.yml"
11+
- "bin/validate-docs.sh"
12+
- "Makefile"
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
validate:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Harden Runner
22+
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
23+
with:
24+
egress-policy: audit
25+
disable-telemetry: true
26+
27+
- name: Checkout
28+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29+
30+
- name: Install asciidoctor
31+
run: sudo gem install asciidoctor --no-document
32+
33+
- name: Validate documentation
34+
run: make validate-docs

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ This is the Conforma User Guide repository, which contains documentation for Con
88

99
## Common Commands
1010

11+
### Documentation Validation
12+
```bash
13+
make validate-docs
14+
```
15+
Lightweight validation that works without external repositories or network access. Checks:
16+
- AsciiDoc syntax errors (requires `asciidoctor`; skips gracefully if not installed)
17+
- Broken `xref:` references to non-existent pages within this component
18+
- Broken `include::` references to non-existent partials
19+
- Navigation references to non-existent pages
20+
21+
Run this after editing any `.adoc` file to catch errors before pushing.
22+
1123
### Documentation Preview
1224
```bash
1325
make ec-docs-preview

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ HACBS_DOCS_REPO=git@github.com:conforma/conforma.github.io.git
1717
$(HACBS_DOCS_DIR):
1818
mkdir $(HACBS_DOCS_DIR) && cd $(HACBS_DOCS_DIR) && git clone $(HACBS_DOCS_REPO) .
1919

20+
validate-docs: ## Validate AsciiDoc syntax, xrefs, and includes (no external deps required)
21+
@bash bin/validate-docs.sh
22+
2023
CURRENT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
2124
ec-docs-preview: $(HACBS_DOCS_DIR) ## Build a preview of the documentation
2225
cd $(HACBS_DOCS_DIR) && \

bin/validate-docs.sh

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5+
MODULES_DIR="${REPO_ROOT}/modules"
6+
PAGES_DIR="${MODULES_DIR}/ROOT/pages"
7+
PARTIALS_DIR="${MODULES_DIR}/ROOT/partials"
8+
NAV_FILE="${MODULES_DIR}/ROOT/nav.adoc"
9+
10+
ERROR_LOG=$(mktemp)
11+
WORK_DIR=$(mktemp -d)
12+
trap 'rm -f "${ERROR_LOG}"; rm -rf "${WORK_DIR}"' EXIT
13+
14+
error() {
15+
echo "ERROR: $1" >&2
16+
echo "x" >> "${ERROR_LOG}"
17+
}
18+
19+
warn() {
20+
echo "WARN: $1" >&2
21+
}
22+
23+
# --- AsciiDoc syntax validation ---
24+
validate_syntax() {
25+
echo "==> Validating AsciiDoc syntax..."
26+
27+
if ! command -v asciidoctor &>/dev/null; then
28+
warn "asciidoctor not found, skipping syntax validation."
29+
warn "Install with: gem install asciidoctor"
30+
return
31+
fi
32+
33+
# Antora's partial$ prefix isn't understood by standalone asciidoctor.
34+
# Resolve it to the actual partials path in a temp copy.
35+
cp -a "${PAGES_DIR}" "${WORK_DIR}/pages"
36+
find "${WORK_DIR}/pages" -name '*.adoc' -exec \
37+
sed -i "s|include::partial\\\$|include::${PARTIALS_DIR}/|g" {} +
38+
39+
local syntax_errors=0
40+
while IFS= read -r -d '' file; do
41+
local basename="${file##*/}"
42+
local output
43+
output=$(asciidoctor \
44+
--failure-level=WARN \
45+
--backend=html5 \
46+
--safe-mode=safe \
47+
--out-file=/dev/null \
48+
"$file" 2>&1) || true
49+
50+
if [[ -n "$output" ]]; then
51+
echo "$output" | while IFS= read -r line; do
52+
echo " $line"
53+
done
54+
syntax_errors=$((syntax_errors + 1))
55+
fi
56+
done < <(find "${WORK_DIR}/pages" -name '*.adoc' -print0)
57+
58+
if [[ ${syntax_errors} -gt 0 ]]; then
59+
error "${syntax_errors} file(s) produced asciidoctor warnings or errors"
60+
fi
61+
}
62+
63+
# --- xref validation ---
64+
validate_xrefs() {
65+
echo "==> Validating xref targets..."
66+
67+
while IFS= read -r -d '' file; do
68+
local relpath="${file#"${REPO_ROOT}/"}"
69+
local matches
70+
matches=$(grep -noP 'xref:([^\[]+)\[' "$file" 2>/dev/null) || continue
71+
72+
while IFS=: read -r lineno match; do
73+
local target="${match#xref:}"
74+
target="${target%\[}"
75+
target="${target%%#*}"
76+
77+
# Skip cross-component xrefs (contain component:module: prefix)
78+
if [[ "$target" == *:* ]]; then
79+
continue
80+
fi
81+
82+
if [[ ! -f "${PAGES_DIR}/${target}" ]]; then
83+
error "${relpath}:${lineno}: broken xref to '${target}' (file not found in pages/)"
84+
fi
85+
done <<< "$matches"
86+
done < <(find "${MODULES_DIR}" -name '*.adoc' -print0)
87+
}
88+
89+
# --- include validation ---
90+
validate_includes() {
91+
echo "==> Validating include targets..."
92+
93+
while IFS= read -r -d '' file; do
94+
local relpath="${file#"${REPO_ROOT}/"}"
95+
local matches
96+
matches=$(grep -noP 'include::[^\[]+\[' "$file" 2>/dev/null) || continue
97+
98+
while IFS=: read -r lineno match; do
99+
local target="${match#include::}"
100+
target="${target%\[}"
101+
102+
if [[ "$target" == partial\$* ]]; then
103+
local partial_name="${target#partial\$}"
104+
if [[ ! -f "${PARTIALS_DIR}/${partial_name}" ]]; then
105+
error "${relpath}:${lineno}: broken include, partial '${partial_name}' not found in partials/"
106+
fi
107+
fi
108+
done <<< "$matches"
109+
done < <(find "${MODULES_DIR}" -name '*.adoc' -print0)
110+
}
111+
112+
# --- nav validation ---
113+
validate_nav() {
114+
echo "==> Validating navigation references..."
115+
116+
if [[ ! -f "${NAV_FILE}" ]]; then
117+
error "Navigation file not found: ${NAV_FILE}"
118+
return
119+
fi
120+
121+
for navfile in "${NAV_FILE}" "${PARTIALS_DIR}/contents.adoc"; do
122+
[[ -f "$navfile" ]] || continue
123+
local relpath="${navfile#"${REPO_ROOT}/"}"
124+
local matches
125+
matches=$(grep -noP 'xref:([^\[]+)\[' "$navfile" 2>/dev/null) || continue
126+
127+
while IFS=: read -r lineno match; do
128+
local target="${match#xref:}"
129+
target="${target%\[}"
130+
target="${target%%#*}"
131+
132+
if [[ "$target" == *:* ]]; then
133+
continue
134+
fi
135+
136+
if [[ ! -f "${PAGES_DIR}/${target}" ]]; then
137+
error "${relpath}:${lineno}: nav references non-existent page '${target}'"
138+
fi
139+
done <<< "$matches"
140+
done
141+
}
142+
143+
echo "Validating documentation in ${REPO_ROOT}..."
144+
echo
145+
146+
validate_syntax
147+
validate_xrefs
148+
validate_includes
149+
validate_nav
150+
151+
echo
152+
error_count=$(wc -l < "${ERROR_LOG}" | tr -d ' ')
153+
if [[ ${error_count} -gt 0 ]]; then
154+
echo "FAILED: ${error_count} error(s) found."
155+
exit 1
156+
else
157+
echo "PASSED: All validation checks passed."
158+
exit 0
159+
fi

0 commit comments

Comments
 (0)