Skip to content

Commit dcf5cd1

Browse files
committed
ci: check reference links, orphans, fences, and size
SKILL.md sends the agent to files under references/, and a path that no longer resolves fails silently: the agent is told to read guidance that is not there and falls back on its own priors instead of erroring. With 24 reference files, every rename is a chance to break one unnoticed. Runs alongside the frontmatter validator rather than inside it, since this checks document integrity, not the skill contract.
1 parent 4cf0b12 commit dcf5cd1

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: docs-integrity
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
documents:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- uses: actions/setup-python@v5
11+
with:
12+
python-version: '3.12'
13+
- name: Check document integrity
14+
run: |
15+
python - <<'PY'
16+
import os, re, sys
17+
18+
SKILL = "SKILL.md"
19+
REF_DIR = "references"
20+
SKILL_MAX_BYTES = 40960
21+
REF_LINK = re.compile(r"references/[A-Za-z0-9._-]+\.md")
22+
23+
def read(path):
24+
with open(path, encoding="utf-8") as fh:
25+
return fh.read()
26+
27+
def markdown_files():
28+
for dirpath, dirnames, filenames in os.walk("."):
29+
dirnames[:] = [d for d in dirnames if d != ".git"]
30+
for name in sorted(filenames):
31+
if name.endswith(".md"):
32+
yield os.path.normpath(os.path.join(dirpath, name))
33+
34+
errors = []
35+
36+
reference_files = sorted(
37+
f"{REF_DIR}/{name}"
38+
for name in os.listdir(REF_DIR)
39+
if name.endswith(".md")
40+
)
41+
42+
# Who cites what: SKILL.md plus every reference file.
43+
citations = {}
44+
for source in [SKILL] + reference_files:
45+
for link in REF_LINK.findall(read(source)):
46+
citations.setdefault(link, set()).add(source)
47+
48+
# 1. Every cited reference path resolves on disk.
49+
for link in sorted(citations):
50+
if not os.path.isfile(link):
51+
for source in sorted(citations[link]):
52+
errors.append(
53+
f"{source}: links to {link}, which does not exist on disk"
54+
)
55+
56+
# 2. Every reference file is cited by SKILL.md or another reference file.
57+
for path in reference_files:
58+
if not citations.get(path, set()) - {path}:
59+
errors.append(
60+
f"{path}: orphaned - not cited by {SKILL} or any other "
61+
f"file under {REF_DIR}/"
62+
)
63+
64+
# 3. Code fences are balanced in every Markdown file.
65+
for path in markdown_files():
66+
fences = sum(
67+
1 for line in read(path).splitlines() if line.startswith("```")
68+
)
69+
if fences % 2:
70+
errors.append(
71+
f"{path}: unbalanced code fences - {fences} lines begin with "
72+
f"three backticks, which is odd; every fence needs a closer"
73+
)
74+
75+
# 4. SKILL.md stays under the size ceiling.
76+
size = os.path.getsize(SKILL)
77+
if size > SKILL_MAX_BYTES:
78+
errors.append(
79+
f"{SKILL}: {size} bytes exceeds the {SKILL_MAX_BYTES}-byte limit "
80+
f"by {size - SKILL_MAX_BYTES} bytes"
81+
)
82+
83+
if errors:
84+
sys.exit(
85+
"Document integrity check failed:\n"
86+
+ "\n".join(f" - {e}" for e in errors)
87+
)
88+
89+
print(
90+
f"OK: {len(reference_files)} reference files, all links resolve, "
91+
f"no orphans, fences balanced, {SKILL} is {size}/{SKILL_MAX_BYTES} bytes"
92+
)
93+
PY

0 commit comments

Comments
 (0)