Skip to content

Commit 6b5dcce

Browse files
committed
Add example index consistency check
1 parent 38a76ca commit 6b5dcce

6 files changed

Lines changed: 122 additions & 0 deletions

File tree

docs/guides/example-indexes.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!--
2+
Copyright (C) 2026 Marco Fortina
3+
SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
# Example and index consistency
7+
8+
Automax keeps examples, generated plugin lists, runtime pack definitions and documentation navigation aligned with `scripts/check-example-indexes.sh`.
9+
10+
```bash
11+
scripts/check-example-indexes.sh
12+
```
13+
14+
The check verifies:
15+
16+
- plugin smoke runbooks against `examples/runbooks/RUNBOOK_INDEX.md`;
17+
- the documented builtin plugin list in `docs/plugins/index.md`;
18+
- guide pages listed in `mkdocs.yml`;
19+
- required runtime validation pack environment files.
20+
21+
Run it before release when examples or plugin documentation change.

docs/reference/release.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The script verifies:
4141

4242
- the `CHANGELOG.md` section for the package version;
4343
- generated plugin reference freshness;
44+
- example and documentation indexes through `scripts/check-example-indexes.sh`;
4445
- plugin smoke runbook index freshness;
4546
- policy/risk preflight for committed examples;
4647
- SQLite database smoke through `scripts/database-smoke.sh`;

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ nav:
7575
- Approval Gates: guides/approval-gates.md
7676
- Runtime Evidence Index: guides/runtime-evidence-index.md
7777
- Package Release Smoke: guides/package-release-smoke.md
78+
- Example Indexes: guides/example-indexes.md
7879
- Run Event Stream: guides/run-event-stream.md
7980
- Publishing Documentation: guides/publishing-docs.md
8081
- Reference:

scripts/check-example-indexes.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
# Copyright (C) 2026 Marco Fortina
3+
# SPDX-License-Identifier: AGPL-3.0-or-later
4+
5+
set -euo pipefail
6+
7+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8+
cd "${ROOT}"
9+
10+
python - <<'PY'
11+
from __future__ import annotations
12+
13+
from pathlib import Path
14+
import re
15+
import yaml
16+
17+
failures: list[str] = []
18+
19+
index_path = Path("examples/runbooks/RUNBOOK_INDEX.md")
20+
index_text = index_path.read_text(encoding="utf-8")
21+
indexed = {
22+
match.group("file"): int(match.group("count"))
23+
for match in re.finditer(
24+
r"\| `(?P<file>runbooks/[^`]+[.]check[.]yaml)` \| [^|]+ \| (?P<count>\d+) \|",
25+
index_text,
26+
)
27+
}
28+
actual = {}
29+
for runbook_path in sorted(Path("examples/runbooks/runbooks").glob("*.check.yaml")):
30+
data = yaml.safe_load(runbook_path.read_text(encoding="utf-8"))
31+
count = sum(
32+
1
33+
for task in data.get("tasks", [])
34+
for step in task.get("steps", [])
35+
for _ in step.get("substeps", [])
36+
)
37+
actual[f"runbooks/{runbook_path.name}"] = count
38+
if indexed != actual:
39+
failures.append(
40+
"examples/runbooks/RUNBOOK_INDEX.md is stale: "
41+
f"missing={sorted(set(actual) - set(indexed))} "
42+
f"extra={sorted(set(indexed) - set(actual))} "
43+
f"stale={sorted(name for name in set(actual) & set(indexed) if actual[name] != indexed[name])}"
44+
)
45+
46+
plugin_index = Path("docs/plugins/index.md").read_text(encoding="utf-8")
47+
listed_plugins = set(re.findall(r"^([a-z][a-z0-9_]*(?:[.][a-z][a-z0-9_]*)+)$", plugin_index, flags=re.MULTILINE))
48+
from automax.core.engine import AutomaxEngine
49+
actual_plugins = set(AutomaxEngine().plugin_registry.names())
50+
if listed_plugins != actual_plugins:
51+
failures.append(
52+
"docs/plugins/index.md plugin list is stale: "
53+
f"missing={sorted(actual_plugins - listed_plugins)} "
54+
f"extra={sorted(listed_plugins - actual_plugins)}"
55+
)
56+
57+
mkdocs = Path("mkdocs.yml").read_text(encoding="utf-8")
58+
for path in sorted(Path("docs/guides").glob("*.md")):
59+
nav = path.as_posix().removeprefix("docs/")
60+
if nav not in mkdocs:
61+
failures.append(f"mkdocs.yml does not include guide: {nav}")
62+
63+
pack_dir = Path("examples/runtime-packs")
64+
required_packs = {"package-only.env", "debian-like.env", "redhat-like.env", "privileged-vm.env"}
65+
actual_packs = {path.name for path in pack_dir.glob("*.env")}
66+
if required_packs - actual_packs:
67+
failures.append(f"missing runtime pack env files: {sorted(required_packs - actual_packs)}")
68+
69+
for pack in sorted(pack_dir.glob("*.env")):
70+
text = pack.read_text(encoding="utf-8")
71+
if "AUTOMAX_RUNTIME_PACK_STEPS" not in text:
72+
failures.append(f"{pack}: missing AUTOMAX_RUNTIME_PACK_STEPS")
73+
74+
if failures:
75+
for failure in failures:
76+
print(f"ERROR: {failure}")
77+
raise SystemExit(1)
78+
print("Example and documentation indexes OK")
79+
PY

scripts/release-check.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ PY
9696
check_release_metadata
9797
check_generated_plugin_reference
9898
check_runbook_index
99+
scripts/check-example-indexes.sh
99100
scripts/policy-risk-preflight.sh --path examples --fail-on high >/dev/null
100101
scripts/database-smoke.sh --engine sqlite --output-dir "${TMP_DIR}/database-smoke" >/dev/null
101102

tests/test_documentation_and_regressions.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,25 @@ def test_runtime_evidence_suite_is_documented_and_optional():
20972097
assert "set AUTOMAX_SSH_HOST and AUTOMAX_SSH_USER" in result.stderr
20982098

20992099

2100+
def test_example_index_check_is_documented_and_release_wired():
2101+
script = Path("scripts/check-example-indexes.sh").read_text(encoding="utf-8")
2102+
release = Path("scripts/release-check.sh").read_text(encoding="utf-8")
2103+
docs = "\n".join(
2104+
path.read_text(encoding="utf-8")
2105+
for path in [
2106+
Path("docs/guides/example-indexes.md"),
2107+
Path("docs/reference/release.md"),
2108+
Path("mkdocs.yml"),
2109+
]
2110+
)
2111+
2112+
assert "examples/runbooks/RUNBOOK_INDEX.md" in script
2113+
assert "docs/plugins/index.md" in script
2114+
assert "examples/runtime-packs" in script
2115+
assert "scripts/check-example-indexes.sh" in release
2116+
assert "Example Indexes: guides/example-indexes.md" in docs
2117+
2118+
21002119
def test_package_release_smoke_script_is_documented_and_release_wired():
21012120
script = Path("scripts/package-release-smoke.sh").read_text(encoding="utf-8")
21022121
release = Path("scripts/release-check.sh").read_text(encoding="utf-8")

0 commit comments

Comments
 (0)