diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000..6ea2db0 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,114 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "claude", + "Claude", + "Anthropic", + "subagent", + "subagents", + "CLAUDE", + "MCP", + "Mermaid", + "frontmatter", + "pre-commit", + "Ruff", + "Bandit", + "EPUB", + "Sonnet", + "Opus", + "Haiku", + "luongnv89", + "claude-howto", + "ModelContextProtocol", + "YAML", + "JSON", + "API", + "DevOps", + "CRUD", + "CLI", + "SSH", + "HTTP", + "HTTPS", + "AWS", + "GitHub", + "GitLab", + "MongoDB", + "PostgreSQL", + "SQLite", + "Redis", + "JWT", + "OAuth", + "CORS", + "REST", + "GraphQL", + "TypeScript", + "JavaScript", + "Python", + "Bash", + "Unix", + "Linux", + "macOS", + "Windows", + "PowerShell", + "VSCode", + "IDE", + "MCP", + "Checks", + "checkpoints", + "THINKING", + "Headless", + "Headless mode", + "Kyrie", + "subprocesses", + "subprocess", + "checklist", + "checklists", + "customizable", + "customization", + "delegated", + "delegating", + "permissioning", + "GOPATH", + "goToDefinition", + "outgoingCalls", + "incomingCalls", + "docstring", + "docstrings", + "EOSMTP", + "OPENSPEC", + "Markdown", + "realtime", + "real-time", + "refactoring", + "runtime", + "namespace", + "namespacing", + "linting", + "pytest", + "Pytest", + "telemetry", + "caching", + "HTTPS", + "repo", + "repos", + "readme", + "README" + ], + "ignoreWords": [ + "luongnv89" + ], + "ignoreRegExpList": [ + "code\\{[^}]*\\}", + "```[\\s\\S]*?```" + ], + "overrides": [ + { + "filename": "**/*.md", + "words": [ + "commandline", + "cmd" + ] + } + ] +} diff --git a/.github/markdown-link-check-config.json b/.github/markdown-link-check-config.json new file mode 100644 index 0000000..fda6c51 --- /dev/null +++ b/.github/markdown-link-check-config.json @@ -0,0 +1,27 @@ +{ + "ignorePatterns": [ + { + "pattern": "^https://twitter.com", + "comment": "Twitter/X links require authentication" + }, + { + "pattern": "^https://x.com", + "comment": "X (Twitter) links require authentication" + }, + { + "pattern": "^https://github.com/[^/]+/claude-howto", + "comment": "Self-referencing links" + } + ], + "replacementPatterns": [ + { + "pattern": "^/", + "replacement": "https://github.com/luongnv89/claude-howto/blob/main/" + } + ], + "timeout": 10000, + "retryOn": [429, 503], + "retryCount": 3, + "fallbackRetryDelay": 5000, + "aliveStatusCodes": [200, 206] +} diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml new file mode 100644 index 0000000..f803496 --- /dev/null +++ b/.github/workflows/docs-check.yml @@ -0,0 +1,340 @@ +name: Documentation Checks + +on: + push: + branches: [main] + paths: + - '**.md' + - '.github/workflows/docs-check.yml' + pull_request: + branches: [main] + paths: + - '**.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + +# 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