Skip to content

Commit 287c905

Browse files
hyperpolymathclaude
andcommitted
chore(docs): unbreak just; gate every .adoc on actually rendering
Two toolchain defects found while verifying #70. 1. `just` was completely broken. build/contractile.just:136 wrote a literal `{{[A-Z_]*}}` grep pattern unescaped, so just parsed it as an interpolation and aborted before running anything — `just --list`, `just docs`, `just quality`, all 124 recipes. `{{{{` is just's escape for a literal `{{`. The file is hand-maintained (see its header), so this fix holds. 2. Nothing checked that .adoc files parse. check-no-md-in-docs.sh enforces the AsciiDoc rule by *extension*; CI's docs job only checked that files *exist*. An unterminated block or malformed table renders to mangled output and ships unnoticed — which is why #69 shipped on hand-counted delimiters. Adds scripts/check-docs-render.sh (renders every .adoc at --failure-level=WARN), wires it into `just docs-check`, `just quality`, and CI's docs job. Also adds `just docs-html` for local reading. 125 of 131 .adoc render clean. The 6 that do not are quarantined in an ALLOWED list rather than skipped silently: they are half-converted Markdown carrying an .adoc extension (a `= Title` bolted onto a Markdown body), which passes the extension check while still being Markdown. All 6 are RSR boilerplate; no game-design doc is affected. No CI impact beyond the new gate: no workflow invokes `just quality` or the dust-* recipes, so unbreaking just cannot turn anything red. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a93311e commit 287c905

4 files changed

Lines changed: 126 additions & 2 deletions

File tree

.github/workflows/quality.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,11 @@ jobs:
6666
else
6767
echo "✅ Core documentation present"
6868
fi
69+
70+
# The check above tests that doc files *exist*; this tests they *parse*.
71+
# Ruby ships preinstalled on ubuntu-latest, so no setup action is needed.
72+
- name: Install asciidoctor
73+
run: gem install asciidoctor --no-document
74+
75+
- name: Check every .adoc renders
76+
run: ./scripts/check-docs-render.sh .

Justfile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ test-all: test e2e aspect bench readiness
187187
@echo "All test categories passed — safe to merge!"
188188

189189
# Run all quality checks
190-
quality: fmt-check lint test
190+
quality: fmt-check lint docs-check test
191191
@echo "All quality checks passed!"
192192

193193
# Fix all auto-fixable issues [reversible: git checkout]
@@ -281,6 +281,23 @@ docs:
281281
just man
282282
@echo "Documentation generated in docs/"
283283

284+
# Check every .adoc actually renders (content-side counterpart to the .md ban)
285+
docs-check:
286+
@./scripts/check-docs-render.sh .
287+
288+
# Render the docs to HTML for local reading [reversible: rm -rf docs/generated/html]
289+
docs-html:
290+
#!/usr/bin/env bash
291+
set -euo pipefail
292+
if ! command -v asciidoctor >/dev/null 2>&1; then
293+
echo "asciidoctor not found. Install with: gem install asciidoctor" >&2
294+
exit 2
295+
fi
296+
mkdir -p docs/generated/html
297+
find docs -name '*.adoc' -type f -print0 \
298+
| xargs -0 asciidoctor -D docs/generated/html
299+
echo "Rendered to docs/generated/html/"
300+
284301
# Generate justfile cookbook documentation
285302
cookbook:
286303
#!/usr/bin/env bash

build/contractile.just

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,11 @@ dust-no-ai-djot:
132132
dust-no-scm-files:
133133
! find .machine_readable/ -name '*.scm' 2>/dev/null | head -1 | grep -q .
134134

135+
# NOTE: `{{{{` is just's escape for a literal `{{`. Writing the pattern
136+
# unescaped makes just parse it as an interpolation and abort — which broke
137+
# *every* recipe in the repo, not merely this one.
135138
dust-no-placeholder-tokens:
136-
! grep -rl '{{[A-Z_]*}}' --include='*.a2ml' --include='*.adoc' --exclude-dir='.git' . 2>/dev/null | head -1 | grep -q .
139+
! grep -rl '{{{{[A-Z_]*}}' --include='*.a2ml' --include='*.adoc' --exclude-dir='.git' . 2>/dev/null | head -1 | grep -q .
137140

138141
dust-no-tracked-artifacts:
139142
! git ls-files lib/bs/ lib/ocaml/ target/release/ _build/ zig-cache/ zig-out/ 2>/dev/null | head -1 | grep -q .

scripts/check-docs-render.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
#
5+
# check-docs-render.sh — every .adoc must actually render.
6+
#
7+
# The estate rule "AsciiDoc by default" is enforced by extension
8+
# (check-no-md-in-docs.sh). Nothing enforced that the *content* parses. A
9+
# malformed table, an unterminated listing block, or a stray level-0 heading
10+
# renders to silently mangled output and ships unnoticed.
11+
#
12+
# This renders every .adoc with --failure-level=WARN and fails on any
13+
# diagnostic. It is the content-side counterpart to the extension-side check.
14+
#
15+
# Known-bad files are quarantined in ALLOWED below rather than silently
16+
# skipped — the list is meant to shrink, and an empty list is the goal.
17+
#
18+
# Exit codes:
19+
# 0 — every non-allow-listed .adoc renders without warnings
20+
# 1 — one or more failed to render cleanly
21+
# 2 — usage / setup error (asciidoctor missing)
22+
23+
set -euo pipefail
24+
25+
REPO_ROOT="${1:-.}"
26+
27+
# Quarantined: half-converted Markdown carrying an .adoc extension (a `= Title`
28+
# line bolted onto a Markdown body — `#` headings, <!-- --> comments, | tables).
29+
# These satisfy check-no-md-in-docs.sh by extension while still being Markdown.
30+
# All are RSR boilerplate, not game design. Fix by converting the body properly,
31+
# then delete the entry. Do not add game-design docs to this list.
32+
ALLOWED=(
33+
"docs/architecture/THREAT-MODEL.adoc"
34+
"docs/decisions/0000-template.adoc"
35+
"docs/decisions/0001-adopt-rsr-standard.adoc"
36+
"docs/developer/ABI-FFI-README.adoc"
37+
"docs/practice/AI-CONVENTIONS.adoc"
38+
"docs/STATE-VISUALIZER.adoc"
39+
)
40+
41+
if ! command -v asciidoctor >/dev/null 2>&1; then
42+
echo "FAIL: asciidoctor not found. Install with: gem install asciidoctor" >&2
43+
exit 2
44+
fi
45+
46+
if [ ! -d "$REPO_ROOT" ]; then
47+
echo "FAIL: not a directory: $REPO_ROOT" >&2
48+
exit 2
49+
fi
50+
51+
mapfile -t FILES < <(find "$REPO_ROOT" -name '*.adoc' -type f -not -path '*/.git/*' | sort)
52+
53+
if [ ${#FILES[@]} -eq 0 ]; then
54+
echo "PASS: no .adoc files found (nothing to check)"
55+
exit 0
56+
fi
57+
58+
BROKEN=()
59+
CLEAN=0
60+
SKIPPED=0
61+
62+
for f in "${FILES[@]}"; do
63+
rel="${f#"$REPO_ROOT/"}"
64+
rel="${rel#./}"
65+
66+
skip=0
67+
for allowed in "${ALLOWED[@]}"; do
68+
if [ "$rel" = "$allowed" ]; then skip=1; break; fi
69+
done
70+
if [ $skip -eq 1 ]; then
71+
SKIPPED=$((SKIPPED + 1))
72+
continue
73+
fi
74+
75+
if out=$(asciidoctor --failure-level=WARN -o /dev/null "$f" 2>&1) && [ -z "$out" ]; then
76+
CLEAN=$((CLEAN + 1))
77+
else
78+
BROKEN+=("$rel")
79+
BROKEN+=("$(echo "$out" | sed 's/^/ /')")
80+
fi
81+
done
82+
83+
if [ ${#BROKEN[@]} -eq 0 ]; then
84+
echo "PASS: $CLEAN .adoc files render cleanly (${SKIPPED} quarantined in ALLOWED)"
85+
exit 0
86+
fi
87+
88+
echo "FAIL: $(( ${#BROKEN[@]} / 2 )) .adoc file(s) do not render cleanly:" >&2
89+
for ((i = 0; i < ${#BROKEN[@]}; i += 2)); do
90+
echo " - ${BROKEN[i]}" >&2
91+
echo "${BROKEN[i + 1]}" >&2
92+
done
93+
echo "" >&2
94+
echo "Fix the markup, or — only for pre-existing boilerplate — add a justified" >&2
95+
echo "entry to the ALLOWED list in scripts/check-docs-render.sh." >&2
96+
exit 1

0 commit comments

Comments
 (0)