Files
claude-howto/.github/workflows/docs-check.yml
T
Luong NGUYEN e8cdd26db0 ci: Trigger docs-check on config file changes
Add .cspell.json and markdown-link-check-config.json to the
docs-check workflow trigger paths so spelling and link check
config changes are properly validated.
2026-01-15 15:06:21 +01:00

345 lines
11 KiB
YAML

name: Documentation Checks
on:
push:
branches: [main]
paths:
- '**.md'
- '.github/workflows/docs-check.yml'
- '.cspell.json'
- '.github/markdown-link-check-config.json'
pull_request:
branches: [main]
paths:
- '**.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
- '.cspell.json'
- '.github/markdown-link-check-config.json'
# Cancel in-progress runs for the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
markdown-lint:
name: Markdown Linting
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install markdownlint-cli
run: npm install -g markdownlint-cli
- name: Run Markdown Linter
run: markdownlint '**/*.md' --ignore node_modules --ignore .venv
continue-on-error: true
link-check:
name: Check Links
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check markdown links
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'no'
config-file: '.github/markdown-link-check-config.json'
spelling:
name: Spell Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run cSpell
uses: streetsidesoftware/cspell-action@v6
with:
files: |
**/*.md
**/*.json
**/*.yml
**/*.yaml
incremental: 'no'
config: '.cspell.json'
frontmatter:
name: YAML Frontmatter Validation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install PyYAML
run: pip install pyyaml
- name: Validate YAML Frontmatter
run: |
python3 << 'EOF'
import os
import yaml
import re
from pathlib import Path
errors = []
# Check all markdown files in Claude sections
md_files = list(Path('.').glob('**/README.md')) + list(Path('.').glob('01-*/**/*.md'))
for file_path in md_files:
if '.venv' in str(file_path) or 'node_modules' in str(file_path):
continue
with open(file_path, 'r') as f:
content = f.read()
# Check for YAML frontmatter in skill/agent templates
if 'SKILL.md' in str(file_path) or any(x in str(file_path) for x in ['agent', 'template']):
if content.startswith('---'):
try:
# Extract frontmatter
parts = content.split('---', 2)
if len(parts) >= 3:
yaml.safe_load(parts[1])
except yaml.YAMLError as e:
errors.append(f"{file_path}: Invalid YAML frontmatter - {e}")
else:
if 'skill' in str(file_path).lower():
print(f"⚠ {file_path}: Consider adding YAML frontmatter")
if errors:
for error in errors:
print(f"❌ {error}")
exit(1)
else:
print("✅ All YAML frontmatter is valid")
EOF
structure:
name: Documentation Structure
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Validate Documentation Structure
run: |
python3 << 'EOF'
import os
from pathlib import Path
import re
errors = []
warnings = []
# Check that all numbered directories have README.md
for i in range(1, 11):
dir_path = Path(f"{i:02d}-*")
matches = list(dir_path.parent.glob(str(dir_path)))
if matches:
for match in matches:
if (match / "README.md").exists():
print(f"✅ {match}/README.md found")
else:
errors.append(f"{match}: Missing README.md")
# Check README.md has required sections
readme_path = Path("README.md")
if readme_path.exists():
with open(readme_path) as f:
content = f.read()
required_sections = [
"## Table of Contents",
"## Contributing",
"## License",
]
for section in required_sections:
if section not in content:
errors.append(f"README.md: Missing '{section}' section")
else:
print(f"✅ README.md has '{section}' section")
# Check for broken relative links
md_files = list(Path(".").rglob("*.md"))
for file_path in md_files:
if ".venv" in str(file_path) or "node_modules" in str(file_path):
continue
with open(file_path) as f:
content = f.read()
# Find markdown links to local files
local_links = re.findall(r'\[([^\]]+)\]\(([^)]+\.md)\)', content)
for link_text, link_path in local_links:
# Skip anchor-only links
if link_path.startswith("#"):
continue
resolved_path = (file_path.parent / link_path).resolve()
if not resolved_path.exists():
warnings.append(f"{file_path}: Broken link to '{link_path}'")
if errors:
print("\n❌ Validation Errors:")
for error in errors:
print(f" - {error}")
exit(1)
if warnings:
print("\n⚠ Warnings:")
for warning in warnings:
print(f" - {warning}")
print("\n✅ Documentation structure is valid")
EOF
metadata:
name: File Metadata Checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for common issues
run: |
set +e # Don't exit on first error
echo "📋 Checking for documentation issues..."
# Check for files over 100KB
echo "Checking file sizes..."
large_files=$(find . -name "*.md" -size +100k | grep -v ".venv" | grep -v "node_modules")
if [ ! -z "$large_files" ]; then
echo "⚠ Large markdown files found (>100KB):"
echo "$large_files"
fi
# Check for TODO markers (might indicate incomplete content)
echo "Checking for TODO markers in main docs..."
todos=$(grep -r "TODO\|FIXME" --include="*.md" . --exclude-dir=.venv --exclude-dir=node_modules --exclude-dir=.git | grep -v ".github" | head -10)
if [ ! -z "$todos" ]; then
echo "⚠ Found TODO/FIXME markers:"
echo "$todos"
fi
# Check for consistent heading structure
echo "Checking heading consistency in key files..."
for file in README.md CONTRIBUTING.md; do
if [ -f "$file" ]; then
heading_count=$(grep -c "^##" "$file")
echo "✅ $file has $heading_count main sections"
fi
done
# Check that LICENSE file exists
if [ -f "LICENSE" ]; then
echo "✅ LICENSE file exists"
else
echo "❌ LICENSE file not found"
exit 1
fi
consistency:
name: Content Consistency
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Check Content Consistency
run: |
python3 << 'EOF'
import re
from pathlib import Path
errors = []
# Check that all numbered sections are referenced in README
readme_path = Path("README.md")
with open(readme_path) as f:
readme_content = f.read()
# Look for numbered directories
for i in range(1, 11):
dir_pattern = f"{i:02d}-"
dirs = list(Path(".").glob(f"{dir_pattern}*"))
if dirs:
for dir_path in dirs:
if dir_path.is_dir():
# Check if referenced in README
dir_name = dir_path.name
if dir_name not in readme_content:
errors.append(f"README.md: Directory '{dir_name}' not mentioned")
else:
print(f"✅ '{dir_name}' is referenced in README")
# Check for consistent code fence formatting
md_files = [f for f in Path(".").rglob("*.md") if ".venv" not in str(f)]
for file_path in md_files:
with open(file_path) as f:
content = f.read()
# Count code fence types
backtick_fences = len(re.findall(r'```', content))
if backtick_fences % 2 != 0:
errors.append(f"{file_path}: Unmatched code fences (backticks)")
if errors:
print("\n❌ Consistency Errors:")
for error in errors:
print(f" - {error}")
exit(1)
print("\n✅ Content is consistent")
EOF
summary:
name: Summary
needs: [markdown-lint, link-check, spelling, frontmatter, structure, metadata, consistency]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check Job Results
run: |
if [ "${{ needs.markdown-lint.result }}" = "failure" ] || \
[ "${{ needs.link-check.result }}" = "failure" ] || \
[ "${{ needs.spelling.result }}" = "failure" ] || \
[ "${{ needs.frontmatter.result }}" = "failure" ] || \
[ "${{ needs.structure.result }}" = "failure" ] || \
[ "${{ needs.metadata.result }}" = "failure" ] || \
[ "${{ needs.consistency.result }}" = "failure" ]; then
echo "❌ Some documentation checks failed"
exit 1
else
echo "✅ All documentation checks passed!"
fi