name: Documentation Automation
on:
push:
branches: [ main ]
paths:
- '**.md'
- 'docs/**'
- 'README.md'
- 'index.js'
- 'package.json'
- 'scripts/docs/**'
pull_request:
branches: [ main ]
paths:
- '**.md'
- 'docs/**'
- 'README.md'
- 'index.js'
- 'package.json'
- 'scripts/docs/**'
schedule:
# Weekly documentation health check
- cron: '0 4 * * 1'
workflow_dispatch:
env:
NODE_VERSION: '18'
permissions:
contents: read
jobs:
validate-docs:
name: Validate Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Validate markdown links
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'yes'
config-file: '.github/markdown-link-check.json'
folder-path: '.'
max-depth: 3
- name: Check for broken internal links
run: |
echo "## π Documentation Link Validation" > link-report.md
echo "" >> link-report.md
# Check if we have any markdown files
if find . -name "*.md" -not -path "./node_modules/*" | head -1 | grep -q .; then
echo "β
Markdown files found, validation completed by link checker" >> link-report.md
else
echo "β οΈ No markdown files found for validation" >> link-report.md
fi
# Check README sections
if grep -q "## Table of Contents\|## Features\|## Installation" README.md; then
echo "β
README.md has proper sections" >> link-report.md
else
echo "β οΈ README.md might be missing standard sections" >> link-report.md
fi
- name: Validate MCP tool documentation
run: |
echo "" >> link-report.md
echo "### π§ MCP Tool Documentation Check" >> link-report.md
echo "" >> link-report.md
# Extract tools from index.js and check if documented
node -e "
const fs = require('fs');
const indexContent = fs.readFileSync('index.js', 'utf8');
const readmeContent = fs.readFileSync('README.md', 'utf8');
// Extract tool definitions (simplified)
const toolRegex = /name:\s*['\"]([^'\"]+)['\"]/g;
const tools = [];
let match;
while ((match = toolRegex.exec(indexContent)) !== null) {
tools.push(match[1]);
}
console.log('Found tools:', tools.join(', '));
let documentedTools = 0;
let undocumentedTools = [];
tools.forEach(tool => {
if (readmeContent.includes(tool)) {
documentedTools++;
} else {
undocumentedTools.push(tool);
}
});
const report = [
'**Tools found in code**: ' + tools.length,
'**Tools documented in README**: ' + documentedTools,
undocumentedTools.length > 0 ?
'**Undocumented tools**: ' + undocumentedTools.join(', ') :
'β
**All tools are documented**'
].join('\n');
fs.writeFileSync('tool-docs-report.txt', report);
"
cat tool-docs-report.txt >> link-report.md
- name: Upload documentation report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: documentation-validation-report
path: link-report.md
retention-days: 30
update-toc:
name: Check Table of Contents
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Check if TOC needs update
run: |
echo "## π Table of Contents Check" > toc-check-report.md
echo "" >> toc-check-report.md
# Simple check for basic README structure
if grep -q "## Table of Contents" README.md; then
echo "β
Table of Contents section exists" >> toc-check-report.md
else
echo "βΉοΈ No Table of Contents found - could be added for better navigation" >> toc-check-report.md
fi
# Check for proper heading structure
heading_count=$(grep -c "^#" README.md || true)
echo "π Found $heading_count headings in README.md" >> toc-check-report.md
if [ $heading_count -gt 5 ]; then
echo "π‘ With $heading_count headings, a Table of Contents would improve navigation" >> toc-check-report.md
fi
- name: Upload TOC check report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: toc-check-report
path: toc-check-report.md
retention-days: 30
check-outdated-docs:
name: Check for Outdated Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
with:
fetch-depth: 0
- name: Check documentation freshness
run: |
echo "## π
Documentation Freshness Report" > freshness-report.md
echo "" >> freshness-report.md
# Check when each markdown file was last updated
echo "### File Last Modified Dates" >> freshness-report.md
echo "" >> freshness-report.md
echo "| File | Last Modified | Days Ago |" >> freshness-report.md
echo "|------|---------------|----------|" >> freshness-report.md
find . -name "*.md" -not -path "./node_modules/*" | while read file; do
if [ -f "$file" ]; then
last_modified=$(git log -1 --format="%ci" -- "$file" 2>/dev/null || echo "Unknown")
if [ "$last_modified" != "Unknown" ]; then
days_ago=$(( ($(date +%s) - $(date -d "$last_modified" +%s)) / 86400 ))
echo "| $file | $last_modified | $days_ago |" >> freshness-report.md
else
echo "| $file | Never committed | N/A |" >> freshness-report.md
fi
fi
done
echo "" >> freshness-report.md
# Check for very old files (>90 days)
echo "### π¨ Potentially Outdated Files" >> freshness-report.md
echo "" >> freshness-report.md
old_files_found=false
find . -name "*.md" -not -path "./node_modules/*" | while read file; do
if [ -f "$file" ]; then
last_modified=$(git log -1 --format="%ci" -- "$file" 2>/dev/null)
if [ -n "$last_modified" ]; then
days_ago=$(( ($(date +%s) - $(date -d "$last_modified" +%s)) / 86400 ))
if [ $days_ago -gt 90 ]; then
echo "β οΈ **$file** - Not updated in $days_ago days" >> freshness-report.md
old_files_found=true
fi
fi
fi
done
if [ "$old_files_found" != "true" ]; then
echo "β
All documentation files are reasonably fresh" >> freshness-report.md
fi
- name: Upload freshness report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: documentation-freshness-report
path: freshness-report.md
retention-days: 30
generate-api-docs:
name: Generate API Documentation
runs-on: ubuntu-latest
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Extract MCP Tools Documentation
run: |
echo "π Extracting MCP tools from source code..."
node scripts/docs/extract-docs.js
- name: Generate HTML Documentation
run: |
echo "ποΈ Generating comprehensive HTML documentation..."
node scripts/docs/generate-tools-html.js
node scripts/docs/generate-landing-page.js
echo "π Documentation generation complete:"
echo "- Landing page: docs/index.html"
echo "- Tools documentation: docs/tools.html"
echo "- Tools data: docs-data/tools.json"
# Show tool count for verification
tool_count=$(node -e "const data = require('./docs-data/tools.json'); console.log(data.toolsCount);")
echo "β
Successfully generated documentation for $tool_count MCP tools"
- name: Create documentation update PR
env:
# Prefer a fine-grained PAT so CI/CodeQL workflows trigger on the PR branch
# Fallback to GITHUB_TOKEN if DOCS_PAT is not configured (PR may require a manual noop commit)
PUSH_TOKEN: ${{ secrets.DOCS_PAT || secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.DOCS_PAT || github.token }}
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Stage all generated documentation files
git add docs/index.html docs/tools.html docs-data/tools.json
# Only proceed if there are changes
if git diff --staged --quiet; then
echo "No documentation changes to commit"
else
# Create a unique branch name
branch_name="docs/auto-update-$(date +%Y%m%d-%H%M%S)"
git checkout -b "$branch_name"
# Get tool count for commit message
tool_count=$(node -e "const data = require('./docs-data/tools.json'); console.log(data.toolsCount);")
git commit -m "docs: auto-update API documentation
- Generated from latest source code
- Updated tools.html with comprehensive API reference
- Updated index.html with current tool count
- Extracted $tool_count MCP tools"
# Push using token-authenticated URL to ensure CI can be triggered when using a PAT
git push "https://$PUSH_TOKEN@github.com/${{ github.repository }}" "$branch_name"
# Create PR using GitHub CLI
if gh pr create \
--title "π Auto-update API documentation" \
--body "## π€ Automated Documentation Update
This PR contains automatically generated documentation updates.
### π Changes
- **Tools documented**: $tool_count MCP tools
- **Generated files**:
- \`docs/index.html\` - Landing page with tool overview
- \`docs/tools.html\` - Comprehensive API documentation
- \`docs-data/tools.json\` - Structured tool data
### π Verification
- β
All tools extracted successfully
- β
HTML documentation generated
- β
Landing page updated with current tool count
### π Review Checklist
- [ ] Tool count matches expected number (currently $tool_count)
- [ ] HTML files are properly formatted
- [ ] No sensitive information exposed
- [ ] Documentation reflects current codebase
**This PR can be safely merged** - it only contains auto-generated documentation." \
--head "$branch_name" \
--base "main" \
--label "documentation" \
--label "automated"; then
echo "β
Documentation PR created: $branch_name"
else
echo "β οΈ Could not create PR automatically (likely due to permissions)"
echo "π Manual PR creation required:"
echo " Branch: $branch_name"
echo " Command: gh pr create --head $branch_name --base main --title 'π Auto-update API documentation' --label documentation --label automated"
echo "π Direct link: https://github.com/${{ github.repository }}/compare/main...$branch_name"
echo "π‘ Tip: Configure a DOCS_PAT repository secret with 'contents:write' and 'pull_request:write' to allow docs PRs to trigger CI checks automatically."
fi
fi
- name: Upload documentation artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: generated-documentation
path: |
docs/index.html
docs/tools.html
docs-data/tools.json
retention-days: 30
spell-check:
name: Spell Check Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Run spell check
uses: streetsidesoftware/cspell-action@76c6f6d52abd57f4bcab5f3fde1bbd4f19a99eb0 # v7
with:
files: "**/*.md"
config: ".github/cspell.json"
inline: "error"
incremental_files_only: false
strict: false
verbose: true
accessibility-check:
name: Documentation Accessibility Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Check markdown accessibility
run: |
echo "## βΏ Accessibility Check Report" > accessibility-report.md
echo "" >> accessibility-report.md
# Check for alt text on images
echo "### πΌοΈ Image Alt Text Check" >> accessibility-report.md
echo "" >> accessibility-report.md
images_without_alt=0
find . -name "*.md" -not -path "./node_modules/*" | xargs grep -n "!\[" | while read line; do
if echo "$line" | grep -q "!\[\]"; then
echo "β οΈ Missing alt text: $line" >> accessibility-report.md
images_without_alt=$((images_without_alt + 1))
fi
done
if [ $images_without_alt -eq 0 ]; then
echo "β
All images have alt text" >> accessibility-report.md
fi
echo "" >> accessibility-report.md
# Check for proper heading hierarchy
echo "### π Heading Hierarchy Check" >> accessibility-report.md
echo "" >> accessibility-report.md
find . -name "*.md" -not -path "./node_modules/*" | while read file; do
echo "**$file:**" >> accessibility-report.md
grep "^#" "$file" | head -5 >> accessibility-report.md || echo "No headings found" >> accessibility-report.md
echo "" >> accessibility-report.md
done
- name: Upload accessibility report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: accessibility-report
path: accessibility-report.md
retention-days: 30
pr-docs-check:
name: PR Documentation Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
pull-requests: write
steps:
- name: Checkout PR
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Check for documentation updates
id: docs-check
run: |
echo "## π Documentation Review" > pr-docs-report.md
echo "" >> pr-docs-report.md
# Check if code changes require doc updates
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "index.js"; then
echo "π **Code changes detected in index.js**" >> pr-docs-report.md
echo "" >> pr-docs-report.md
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "\.md$"; then
echo "β
Documentation files were also updated" >> pr-docs-report.md
else
echo "β οΈ **Consider updating documentation** - Code changes detected but no markdown files updated" >> pr-docs-report.md
fi
echo "" >> pr-docs-report.md
fi
# Check for new MCP tools
new_tools=$(git diff origin/${{ github.base_ref }}...HEAD index.js | grep "^+" | grep -c "name:" || echo "0")
removed_tools=$(git diff origin/${{ github.base_ref }}...HEAD index.js | grep "^-" | grep -c "name:" || echo "0")
if [ "$new_tools" -gt 0 ] || [ "$removed_tools" -gt 0 ]; then
echo "π§ **MCP Tool Changes Detected:**" >> pr-docs-report.md
echo "- New tools: $new_tools" >> pr-docs-report.md
echo "- Removed tools: $removed_tools" >> pr-docs-report.md
echo "" >> pr-docs-report.md
echo "π **Please ensure README.md reflects these changes**" >> pr-docs-report.md
echo "" >> pr-docs-report.md
fi
- name: Comment on PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7
with:
script: |
const fs = require('fs');
try {
const report = fs.readFileSync('pr-docs-report.md', 'utf8');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
} catch (error) {
console.error('Error posting documentation review:', error);
}
summary:
name: Documentation Summary
runs-on: ubuntu-latest
needs: [validate-docs, check-outdated-docs, spell-check, accessibility-check, generate-api-docs]
if: always()
steps:
- name: Create summary
run: |
echo "## π Documentation Automation Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Validation**: ${{ needs.validate-docs.result }}" >> $GITHUB_STEP_SUMMARY
echo "**Freshness Check**: ${{ needs.check-outdated-docs.result }}" >> $GITHUB_STEP_SUMMARY
echo "**Spell Check**: ${{ needs.spell-check.result }}" >> $GITHUB_STEP_SUMMARY
echo "**Accessibility**: ${{ needs.accessibility-check.result }}" >> $GITHUB_STEP_SUMMARY
echo "**API Documentation**: ${{ needs.generate-api-docs.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "π **All reports uploaded as artifacts**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Analysis completed at**: $(date -u)" >> $GITHUB_STEP_SUMMARY