diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5cecbd5f1..be43b3a5a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,8 +24,8 @@ jobs: - name: Install dependencies run: | - pip install requests>=2.32.0 - pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml coverage + python3 -m pip install requests>=2.32.0 + python3 -m pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml coverage - name: Generate coverage run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b09bc8c4e..a6665ab68 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,8 +23,8 @@ jobs: - name: Install dependencies run: | - pip install requests>=2.32.0 - pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml + python3 -m pip install requests>=2.32.0 + python3 -m pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml - name: Run unit tests run: | @@ -53,7 +53,7 @@ jobs: - name: Generate coverage reports run: | if [ -f "coverage.xml" ]; then - pip install coverage + python3 -m pip install coverage # Generate HTML coverage report coverage html -d htmlcov # Generate JSON coverage report @@ -66,7 +66,7 @@ jobs: - name: Check coverage threshold run: | if [ -f "coverage.xml" ]; then - pip install coverage + python3 -m pip install coverage coverage report --fail-under=75 || echo "Coverage below 75% threshold - current: 78.7%" else echo "Skipping coverage check - no coverage data" @@ -137,7 +137,7 @@ jobs: run: | # Test that enhanced mode works with Python bash -c 'set -e - pip install requests>=2.32.0 + python3 -m pip install requests>=2.32.0 # Verify Python scripts exist if [ -f "scripts/main.py" ] && [ -f "scripts/lookup_paths.py" ]; then diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index 8fe6262cb..b78fadc76 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -27,8 +27,8 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r scripts/requirements.txt + python3 -m pip install --upgrade pip + python3 -m pip install -r scripts/requirements.txt # ================================================================= # SAFEGUARD: Count files BEFORE fetch to detect mass deletions @@ -46,7 +46,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_REF_NAME: ${{ github.ref_name }} run: | - python scripts/fetch_claude_docs.py || echo "fetch_failed=true" >> $GITHUB_OUTPUT + python3 scripts/fetch_claude_docs.py || echo "fetch_failed=true" >> $GITHUB_OUTPUT continue-on-error: true # ================================================================= @@ -93,6 +93,14 @@ jobs: echo "✅ Safeguard validation passed: $AFTER files present" + - name: Build search index + if: steps.validate-safeguard.outputs.safeguard_triggered != 'true' + continue-on-error: true + run: | + echo "Building full-text search index..." + python3 scripts/build_search_index.py || echo "⚠️ Search index build failed — content search may be unavailable" + echo "✅ Search index step complete" + - name: Check for changes id: verify-changed-files run: | @@ -103,7 +111,7 @@ jobs: id: commit-msg run: | # Stage changes to see what will be committed - git add -A docs/ + git add -A docs/ paths_manifest.json # Get list of changed files with proper quoting # Using printf with %q for shell-safe quoting would be ideal, but not available in all shells diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d75f828a5..cc41836b8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -19,11 +19,11 @@ jobs: - name: Install dependencies run: | - pip install requests>=2.32.0 - pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml + python3 -m pip install requests>=2.32.0 + python3 -m pip install pytest pytest-cov pytest-asyncio pytest-mock pyyaml - name: Validate all paths reachable - run: python scripts/lookup_paths.py --validate-all + run: python3 scripts/lookup_paths.py --validate-all - name: Run validation tests run: | diff --git a/.gitignore b/.gitignore index a439362e9..b0ebb222a 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,9 @@ upstream/ # Archive folder (legacy/orphaned scripts) archive/ +# Plan documents (local development only, contain local paths) +docs/plans/ + # ============================================================================ # Documentation & Tracking Files (DO NOT COMMIT) # ============================================================================ diff --git a/CLAUDE.md b/CLAUDE.md index 642f285c9..73911795b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,10 +21,10 @@ The docs are periodically updated via GitHub Actions with safeguards to prevent This repository uses a **graceful degradation** approach: **Installation** (always the same): -- 573 documentation paths tracked in manifest (6 categories) -- 571 files downloaded +- Documentation paths tracked in manifest (6 categories) +- Documentation files downloaded - Python scripts for enhanced features -- Full test suite (294 tests) and GitHub workflows +- Full test suite (303 tests) and GitHub workflows **Runtime Features** (Python-dependent): - **Without Python 3.9+**: Basic documentation reading via shell scripts @@ -395,7 +395,7 @@ When Python 3.9+ is installed, these additional capabilities are available: - **Full-text search**: `--search "keyword"` searches across all documentation content - **Category filtering**: `--category api` lists paths in specific categories - **Path validation**: `--validate` checks documentation integrity -- **Active documentation**: Access to 573 paths across 6 categories: +- **Active documentation**: Access to paths across 6 categories: - API Reference (377 paths, 65.8%) - Includes multi-language SDK docs (Python, TypeScript, Go, Java, Kotlin, Ruby) - Core Documentation (82 paths, 14.3%) - Prompt Library (65 paths, 11.3%) @@ -409,7 +409,7 @@ See `enhancements/` directory for comprehensive feature documentation and exampl ``` / -├── docs/ # 571 documentation files (.md format) +├── docs/ # Documentation files (.md format) │ ├── docs_manifest.json # File tracking manifest │ └── .search_index.json # Full-text search index (Python-generated) ├── scripts/ @@ -434,14 +434,14 @@ See `enhancements/` directory for comprehensive feature documentation and exampl │ ├── validation.py # Path validation │ ├── formatting.py # Output formatting │ └── cli.py # Main entry point -├── paths_manifest.json # Active paths manifest (573 paths, 6 categories) +├── paths_manifest.json # Active paths manifest (6 categories) ├── archive/ # Archived/deprecated scripts (git-ignored) ├── enhancements/ # Feature documentation │ ├── README.md # Overview │ ├── FEATURES.md # Technical specs │ ├── CAPABILITIES.md # Detailed capabilities │ └── EXAMPLES.md # Usage examples -├── tests/ # Test suite (294 tests, 294 passing) +├── tests/ # Test suite (303 tests, 303 passing) ├── install.sh # Installation script └── CLAUDE.md # This file (AI context) @@ -464,8 +464,8 @@ When working on this repository: @scripts/fetcher/ - Documentation fetching package (8 modules) @scripts/lookup/ - Search & validation package (7 modules) @scripts/build_search_index.py - Full-text search indexing -@paths_manifest.json - Active paths manifest (573 paths, 6 categories) -@tests/ - Test suite (294 tests) +@paths_manifest.json - Active paths manifest (6 categories) +@tests/ - Test suite (303 tests) ### Automation @.github/workflows/ - Auto-update workflows (runs every 3 hours) @@ -525,7 +525,7 @@ python3 scripts/lookup_paths.py --search "mcp" pytest tests/ -v # Run full test suite -pytest tests/ -q # Should see: 294 passed, 2 skipped +pytest tests/ -q # Should see: 303 passed ``` ## Upstream Compatibility diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bccad4c1..be7e1ca33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,16 +7,16 @@ Thank you for contributing to the Enhanced Claude Code Documentation Mirror! This project extends [ericbuess/claude-code-docs](https://github.com/ericbuess/claude-code-docs) with optional Python features: **Core Principle: Graceful Degradation** -- Single installation (573 paths tracked across 6 categories + Python scripts) +- Single installation (paths tracked across 6 categories + Python scripts) - Python features activate only when Python 3.9+ is available - Everything works without Python (basic `/docs` command) - No separate "modes" - just feature detection at runtime **Design Goals:** -1. **Honesty**: Accurate claims about what we deliver (573 paths tracked, 571 files downloaded) +1. **Honesty**: Accurate claims about what we deliver (paths tracked in manifest, files downloaded) 2. **Simplicity**: One installation, automatic feature detection 3. **Compatibility**: Works with upstream, same `/docs` interface -4. **Testing**: All changes tested (294 tests) +4. **Testing**: All changes tested (303 tests) ## Repository URL Strategy @@ -125,7 +125,7 @@ pip install -e ".[dev]" ~/.claude-code-docs/claude-docs-helper.sh --validate # Run tests (REQUIRED before submitting PR) -pytest tests/ -v # Should see: 294 passed, 2 skipped +pytest tests/ -v # Should see: 303 passed # Test specific changes python scripts/lookup_paths.py "your test query" @@ -136,7 +136,7 @@ python scripts/fetch_claude_docs.py --help - `scripts/fetch_claude_docs.py` - Documentation fetcher with auto-regeneration - `scripts/lookup_paths.py` - Search & validation - `scripts/build_search_index.py` - Full-text search indexing -- `tests/` - Test suite (629 tests) +- `tests/` - Test suite (303 tests) ## Code Standards @@ -330,9 +330,8 @@ def test_search_paths_with_limit(): ``` **Current test status:** -- Total: 296 tests -- Passing: 294 (99.3%) -- Skipped: 2 (intentional) +- Total: 303 tests +- Passing: 303 (100%) ## Pull Request Guidelines @@ -415,7 +414,7 @@ git push origin v0.x.x **When to release:** - New Python features complete -- All tests passing (294/296 or 296/296) +- All tests passing (303/303) - Documentation updated **Process:** diff --git a/README.md b/README.md index 510d961c6..533f867bc 100644 --- a/README.md +++ b/README.md @@ -1,451 +1,121 @@ # Claude Code Documentation Tool [![Last Update](https://img.shields.io/github/last-commit/costiash/claude-code-docs/main.svg?label=docs%20updated)](https://github.com/costiash/claude-code-docs/commits/main) -[![Tests](https://img.shields.io/badge/tests-294%20passing-success)](https://github.com/costiash/claude-code-docs/actions) -[![Coverage](https://img.shields.io/badge/coverage-17.6%25-yellow)](https://github.com/costiash/claude-code-docs) +[![Tests](https://github.com/costiash/claude-code-docs/actions/workflows/test.yml/badge.svg)](https://github.com/costiash/claude-code-docs/actions) [![Python](https://img.shields.io/badge/python-3.9+-blue)](https://www.python.org/) [![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey)](https://github.com/costiash/claude-code-docs) [![Mentioned in Awesome Claude Code](https://awesome.re/mentioned-badge.svg)](https://github.com/hesreallyhim/awesome-claude-code) -> **⭐ This is an enhanced fork of [ericbuess/claude-code-docs](https://github.com/ericbuess/claude-code-docs)** -> -> Built on Eric Buess's excellent foundation, this fork adds Python-powered search, validation, and auto-regeneration features while maintaining graceful degradation - everything works with or without Python. -> -> **For the original, simpler implementation:** [ericbuess/claude-code-docs](https://github.com/ericbuess/claude-code-docs) +> **Enhanced fork of [ericbuess/claude-code-docs](https://github.com/ericbuess/claude-code-docs)** — adds Python-powered search, validation, and auto-regeneration while maintaining graceful degradation. ---- - -**Fast, searchable access to Claude Code documentation - locally, always up-to-date.** - -Stop hunting through scattered docs. This tool provides instant access to **573 actively maintained** Claude documentation paths covering API references, guides, examples, and changelogs. +**Fast, searchable access to Claude documentation — locally, always up-to-date.** ## Key Features -- 🤖 **AI-Powered Search** - Ask questions naturally, Claude understands intent and routes intelligently -- 📚 **Complete Coverage** - 573 documentation paths tracked, 571 files downloaded -- 🔍 **Semantic Understanding** - No primitive keyword matching, leverages Claude's language understanding -- ✅ **Auto-Validated** - Continuous validation detects broken links automatically -- 🔄 **Always Fresh** - Repository updated every 3 hours; run `/docs -t` to pull latest -- 🎯 **Graceful Degradation** - Works with or without Python -- 🧪 **Well-Tested** - 296 tests (294 passing, 2 skipped) - -## How It Works - -This tool takes a different approach to documentation access: - -1. **Local Mirror** - Instead of fetching docs from the web each time, we keep a local copy that's always ready -2. **AI as the Interface** - You ask questions in plain English, Claude figures out which docs to read -3. **Smart Routing** - The `/docs` command understands context ("hooks in agent sdk" vs "cli hooks") -4. **Works Offline** - Once installed, docs are available even without internet - -The magic is in combining a simple local file system with Claude's language understanding. No complex search engines or databases - just markdown files and AI smarts. - -## What's Included - -**Documentation Paths** (573 tracked in manifest across 6 categories): -- API Reference (377 paths, 65.8%) - Complete API docs, Admin API, Agent SDK - - 🐍 **Python** (45 docs) | 📘 **TypeScript** (45 docs) | 🔷 **Go** (45 docs) - - ☕ **Java** (45 docs) | 🟣 **Kotlin** (45 docs) | 💎 **Ruby** (45 docs) -- Core Documentation (82 paths, 14.3%) - Guides, tutorials, best practices -- Prompt Library (65 paths, 11.3%) - Ready-to-use prompt templates -- Claude Code (46 paths, 8.0%) - CLI-specific docs, hooks, skills, MCP -- Release Notes (2 paths) - Version history -- Resources (1 path) - Additional resources - -> 💡 **Multi-language support**: Whether you're building with Python, TypeScript, Go, Java, Kotlin, or Ruby - the API documentation for your language is included and searchable! - -> 🚀 **No Python required!** Core features including AI-powered semantic search work with just bash. Python 3.9+ enables advanced full-text search and path validation. - -**Files Downloaded** (571 actual .md files) - -**Optional Python Features** (requires Python 3.9+): -- Full-text content search (`--search-content`) -- Fuzzy path matching (`--search`) -- HTTP validation (`--validate`) -- Auto-regeneration of manifests +- **AI-Powered Search** — Ask questions naturally via `/docs`, Claude routes intelligently +- **Complete Coverage** — 6 categories of documentation paths tracked and downloaded as markdown +- **Always Fresh** — Auto-updated every 3 hours via GitHub Actions; run `/docs -t` to pull latest +- **Graceful Degradation** — Works with or without Python 3.9+ +- **Multi-Language SDK Docs** — Python, TypeScript, Go, Java, Kotlin, Ruby ## Installation -### Quick Install (2 minutes) - -**One command:** ```bash curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh | bash ``` -**What it does:** -1. Clones repository to `~/.claude-code-docs` -2. Installs 571 documentation files -3. Sets up `/docs` command in Claude Code -4. Verifies installation integrity +This clones the repository to `~/.claude-code-docs`, installs documentation files, and sets up the `/docs` command. Python features activate automatically if Python 3.9+ is available. -**Python features activate automatically if Python 3.9+ is installed.** - -### Installation Methods - -**Method 1: Direct Install (interactive)** -```bash -curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh | bash -``` -Works on: Local terminals, iTerm2, Terminal.app, SSH with `-t` flag - -**Method 2: Auto-Install (CI/CD-friendly)** +**CI/CD or non-interactive environments:** ```bash CLAUDE_DOCS_AUTO_INSTALL=yes curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh | bash ``` -Works on: **All environments** including GitHub Actions, Docker, cron jobs, SSH without `-t` - -**Method 3: Download First (most reliable)** -```bash -curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh -o install.sh -bash install.sh -``` -Works on: All interactive shells - -### Requirements - -- **Required**: macOS 12+ or Linux (Ubuntu, Debian, Fedora, etc.) -- **Required**: git, jq, curl (usually pre-installed) -- **Optional**: Python 3.9+ (enables search/validation features) - -## Upgrading - -### From v0.4.x to v0.5.0 -Version 0.5.0 includes significant improvements requiring a fresh installation: - -```bash -# Re-run the installer - it handles everything automatically -curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh | bash -``` - -The installer will: -1. Detect your existing v0.4.x installation -2. Show you what's changing (version, doc count, new features) -3. Perform an atomic upgrade -4. Verify the new installation - -**What You Get:** - -| Metric | v0.4.x | v0.5.0 | -|--------|--------|--------| -| Documentation Files | ~270 | 571 | -| Tracked Paths | 273 | 573 | -| Python Modules | Monolithic | Modular (15 modules) | -| Safety Thresholds | None | 3 safeguards | - -**What Changes:** -- Filename convention: `docs__en__hooks.md` → `claude-code__hooks.md` -- Script structure: Flat files → Organized packages (`fetcher/`, `lookup/`) - -**What Stays the Same:** -- `/docs` command interface -- All user configs in `~/.claude/` -- Python feature detection (3.9+ optional) +**Requirements:** macOS 12+ or Linux, git, jq, curl. Python 3.9+ optional. ## Usage -### Basic Commands - -**Quick access (no freshness check):** ```bash -/docs hooks # Read hooks documentation instantly -/docs mcp # Read MCP documentation -/docs memory # Read memory features +/docs hooks # Read hooks documentation +/docs mcp # Read MCP documentation +/docs -t # Check sync status and pull updates +/docs what's new # Recent documentation changes +/docs changelog # Official Claude Code release notes ``` -**With freshness check:** -```bash -/docs -t # Check sync status with GitHub -/docs -t hooks # Check sync, then read hooks docs -``` +### Natural Language Queries -**Special commands:** -```bash -/docs what's new # Show recent documentation changes with diffs -/docs changelog # Read official Claude Code release notes -/docs uninstall # Get uninstall command -``` - -### AI-Powered Natural Language Queries - -**The `/docs` command is AI-powered** - it leverages Claude's semantic understanding instead of primitive keyword matching. Ask questions naturally and Claude will intelligently route to the appropriate search functions. - -**How it works:** -1. Claude analyzes your request semantically -2. Determines if you want direct documentation, content search, or path discovery -3. Routes to appropriate helper functions automatically -4. Presents results naturally with context - -**Examples:** +The `/docs` command leverages Claude's semantic understanding — ask questions in plain English: ```bash -# Complex semantic queries -/docs what are the best practices and recommended workflows using Claude Agent SDK in Python according to the official documentation? -→ Claude extracts: "best practices workflows Agent SDK Python" -→ Executes content search automatically -→ Returns relevant documentation with natural explanations - -# Questions about features -/docs what environment variables exist and how do I use them? -→ Claude searches documentation content -→ Provides answer with documentation links - -# Comparative questions +/docs what are the best practices for Agent SDK in Python? /docs explain the differences between hooks and MCP -→ Claude searches for both topics -→ Compares and explains naturally - -# Discovery queries /docs show me everything about memory features -→ Claude finds memory-related documentation -→ Lists and summarizes available docs - -# Topic-specific searches -/docs find all mentions of authentication -→ Claude performs content search -→ Returns matching documentation sections - -# Combined workflows -/docs -t what's new with extended thinking and how does it work? -→ Claude checks for updates -→ Searches for extended thinking documentation -→ Combines recent changes with explanation +/docs how do I use extended thinking? ``` -**Behind the scenes:** Claude itself is the search engine. It can: -- Read documentation files directly -- Search content using grep -- Match filenames to topics -- Synthesize answers from multiple sources +Claude analyzes your intent, searches relevant documentation, synthesizes answers from multiple sources, and presents results with links. -**With Python 3.9+:** Optimized helper commands (`--search-content`, `--search`) provide faster, more accurate results. - -**Without Python 3.9+:** Claude uses its native capabilities (file reading, grep, pattern matching) to find and present documentation. The AI-powered experience works either way - Python just makes it faster. - -### Advanced Commands (Direct Access) - -For power users who want direct access to helper functions: +### Advanced Commands (Python 3.9+) ```bash -# Fuzzy search across 573 paths (requires Python 3.9+) -~/.claude-code-docs/claude-docs-helper.sh --search "keyword" - -# Full-text content search (requires Python 3.9+) -~/.claude-code-docs/claude-docs-helper.sh --search-content "term" - -# Validate all paths - check for 404s (requires Python 3.9+) -~/.claude-code-docs/claude-docs-helper.sh --validate - -# Show installation status and available features -~/.claude-code-docs/claude-docs-helper.sh --status - -# Show all commands -~/.claude-code-docs/claude-docs-helper.sh --help -``` - -**Note:** Most users should use the AI-powered `/docs` command instead of calling these directly. The AI provides better results through semantic understanding and intelligent routing. - -## Architecture - -**Single Installation** - Always installs complete repository: -- 573 documentation paths tracked in manifest (6 categories) -- 571 files downloaded -- Modular Python packages for enhanced features -- Full test suite (294 tests) - -**Modular Code Structure** - Python code organized into focused packages: -``` -scripts/ -├── fetcher/ # Documentation fetching (8 modules) -│ ├── config.py # Constants and safety thresholds -│ ├── manifest.py # Manifest file operations -│ ├── paths.py # Path conversion and categorization -│ ├── sitemap.py # Sitemap discovery and parsing -│ ├── content.py # Content fetching and validation -│ ├── safeguards.py # Safety checks (deletion prevention) -│ └── cli.py # Main entry point -├── lookup/ # Search and validation (7 modules) -│ ├── config.py # Configuration constants -│ ├── manifest.py # Manifest loading utilities -│ ├── search.py # Search functionality -│ ├── validation.py # Path validation -│ ├── formatting.py # Output formatting -│ └── cli.py # Main entry point -├── fetch_claude_docs.py # Thin wrapper (backwards compatible) -└── lookup_paths.py # Thin wrapper (backwards compatible) +~/.claude-code-docs/claude-docs-helper.sh --search "keyword" # Fuzzy path search +~/.claude-code-docs/claude-docs-helper.sh --search-content "term" # Full-text content search +~/.claude-code-docs/claude-docs-helper.sh --validate # Check all paths for 404s +~/.claude-code-docs/claude-docs-helper.sh --status # Installation status ``` -**Graceful Degradation** - Features adapt to environment: -- **Without Python**: Basic documentation reading via `/docs` command -- **With Python 3.9+**: Full-text search, fuzzy matching, validation, auto-regeneration - -**No separate "modes"** - Everything is installed once, features activate when Python is available. - ## How Updates Work -Documentation stays current through: +1. **Automatic** — GitHub Actions fetches from Anthropic sitemaps every 3 hours +2. **On-Demand** — `/docs -t` checks for and pulls updates +3. **Manual** — `cd ~/.claude-code-docs && git pull` +4. **Safe** — Sync safeguards prevent mass deletion (min discovery threshold, max 10% deletion per sync, min file count) -1. **Repository Updates** - GitHub Actions fetches new docs every 3 hours -2. **On-Demand Sync** - Run `/docs -t` to check for and pull updates -3. **Auto-Regeneration** - Manifests regenerate from sitemaps on each fetch -4. **Visual Feedback** - See "🔄 Updating documentation..." when updates occur -5. **Safety Validation** - Each sync validates against safeguard thresholds before committing +## Documentation Categories -**Sitemap Sources:** -- `https://platform.claude.com/sitemap.xml` - Platform documentation -- `https://code.claude.com/docs/sitemap.xml` - Claude Code documentation +Documentation is organized across 6 categories (counts update automatically with each sync): -**Manual update:** -```bash -cd ~/.claude-code-docs && git pull -``` +- **API Reference** — Complete API docs, Admin API, Agent SDK, multi-language SDK docs +- **Core Documentation** — Guides, tutorials, prompt engineering, best practices +- **Claude Code** — CLI-specific docs: hooks, skills, MCP, settings, plugins +- **Prompt Library** — Ready-to-use prompt templates +- **Release Notes** — Version history +- **Resources** — Additional resources -**Force reinstall:** -```bash -curl -fsSL https://raw.githubusercontent.com/costiash/claude-code-docs/main/install.sh | bash -``` +Run `~/.claude-code-docs/claude-docs-helper.sh --status` to see current counts. ## Troubleshooting -### Command Not Found - -**Problem:** `/docs` returns "command not found" - -**Solution:** -1. Check: `ls ~/.claude/commands/docs.md` -2. Restart Claude Code -3. Re-run installer if needed - -### Installation Errors - -**"Installation cancelled" when using `curl | bash`:** - -The installer needs to read your response, but stdin is consumed by the pipe in some environments. - -**Solutions:** -1. Auto-install: `CLAUDE_DOCS_AUTO_INSTALL=yes curl ... | bash` -2. Download first: `curl ... -o install.sh && bash install.sh` -3. SSH with `-t`: `ssh -t user@server 'curl ... | bash'` - -**"Running in non-interactive mode":** - -This appears in CI/CD, Docker, cron, or SSH without `-t`. Use `CLAUDE_DOCS_AUTO_INSTALL=yes`. - -**Other issues:** -- **"git/jq/curl not found"**: Install the missing tool -- **"Failed to clone"**: Check internet connection -- **"Failed to update settings.json"**: Check file permissions - -### Documentation Not Updating - -**Problem:** Documentation seems outdated - -**Solution:** -1. `/docs -t` to force check and update -2. Manual: `cd ~/.claude-code-docs && git pull` -3. Check [GitHub Actions](https://github.com/costiash/claude-code-docs/actions) -4. Reinstall as last resort - -### Which Version? - -Check your installation: -```bash -~/.claude-code-docs/claude-docs-helper.sh --version -``` - -Or: -```bash -cat ~/.claude-code-docs/README.md | head -1 -``` - -## Platform Support - -- ✅ **macOS**: Fully supported (tested on macOS 12+) -- ✅ **Linux**: Fully supported (Ubuntu, Debian, Fedora, etc.) -- ⏳ **Windows**: Not yet supported - [contributions welcome](#contributing)! +| Problem | Solution | +|---------|----------| +| `/docs` not found | Check `ls ~/.claude/commands/docs.md`, restart Claude Code | +| "Installation cancelled" with `curl \| bash` | Use `CLAUDE_DOCS_AUTO_INSTALL=yes` or download first | +| Docs seem outdated | `/docs -t` to force update, or `cd ~/.claude-code-docs && git pull` | +| Check version | `~/.claude-code-docs/claude-docs-helper.sh --version` | ## Uninstalling -Complete removal: -```bash -/docs uninstall -``` - -Or manually: ```bash ~/.claude-code-docs/uninstall.sh ``` -See [UNINSTALL.md](UNINSTALL.md) for manual removal instructions. - ## Security -**Defense-in-Depth Approach:** -- Input sanitization (alphanumeric + safe chars only) -- Path traversal protection (prevents `../` attacks) -- Shell injection prevention (heredocs, env vars) -- Comprehensive security testing (13 test cases) - -**Documentation Deletion Safeguards:** - -The automated sync system includes multiple safeguards to prevent catastrophic documentation loss: - -| Safeguard | Threshold | Purpose | -|-----------|-----------|---------| -| `MIN_DISCOVERY_THRESHOLD` | 200 paths | Refuses to proceed if sitemap discovery finds too few paths | -| `MAX_DELETION_PERCENT` | 10% | Never deletes more than 10% of existing files in one sync | -| `MIN_EXPECTED_FILES` | 250 files | Refuses if file count would drop below minimum | -| Workflow validation | Auto-revert | GitHub Actions automatically reverts on sync failure | - -These safeguards protect against: -- Sitemap URLs returning errors (500, 401, etc.) -- Network failures during discovery -- Upstream documentation restructuring -- Accidental mass deletion bugs - -**Operational Security:** -- All operations limited to documentation directory -- No external data transmission -- HTTPS-only GitHub clones -- You can fork and install from your own repository - -**Validation:** -- 294/296 tests passing (99.3% pass rate, 2 skipped) -- Automated security testing in CI/CD +- Input sanitization and path traversal protection +- Sync safeguards prevent catastrophic documentation loss (min thresholds, max deletion limits, auto-revert) +- All operations limited to documentation directory, HTTPS-only +- Full test suite with security test coverage ## Contributing -Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for: -- Architecture overview -- Development setup -- Testing requirements -- PR guidelines -- Security standards - -**Quick start for contributors:** -```bash -# Fork the repository -git clone https://github.com/YOUR_USERNAME/claude-code-docs.git -cd claude-code-docs - -# Setup Python environment (optional, for enhanced features) -python3 -m venv .venv -source .venv/bin/activate -pip install -e ".[dev]" - -# Run tests -pytest tests/ -v # Should see: 294 passed, 2 skipped -``` +See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture overview, development setup, testing requirements, and PR guidelines. ## Acknowledgments -- **[Eric Buess](https://github.com/ericbuess)** - Creator of [claude-code-docs](https://github.com/ericbuess/claude-code-docs), the foundation for this project -- **[Anthropic](https://www.anthropic.com/)** - For Claude Code and the documentation - -The original [ericbuess/claude-code-docs](https://github.com/ericbuess/claude-code-docs) provides a simpler, shell-only implementation. This fork extends it with optional Python features for users who need advanced search and validation. +- **[Eric Buess](https://github.com/ericbuess)** — Creator of the [original claude-code-docs](https://github.com/ericbuess/claude-code-docs) +- **[Anthropic](https://www.anthropic.com/)** — For Claude Code and the documentation ## License -Documentation content belongs to Anthropic. -Tool code is open source - contributions welcome! +Documentation content belongs to Anthropic. Tool code is open source. diff --git a/install.sh b/install.sh index aeb84608a..704cc9404 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -# Claude Code Docs Installer v0.5.0 - Enhanced edition with breaking changes +# Claude Code Docs Installer v0.5.1 - Bug fixes and CI improvements # This script installs claude-code-docs to ~/.claude-code-docs # Installation Strategy: Always perform a fresh installation at the fixed location # 1. Remove any existing installation at ~/.claude-code-docs (with user confirmation) @@ -9,12 +9,12 @@ set -euo pipefail # 3. Set up commands and hooks # 4. Clean up any old installations in other locations -echo "Claude Code Docs Installer v0.5.0" +echo "Claude Code Docs Installer v0.5.1" echo "===============================" # Target version for upgrade messaging -TARGET_VERSION="0.5.0" -TARGET_DOCS="571" +TARGET_VERSION="0.5.1" +TARGET_DOCS="" # Set after install from DOC_COUNT # Fixed installation location INSTALL_DIR="$HOME/.claude-code-docs" @@ -92,14 +92,14 @@ show_upgrade_info() { echo "════════════════════════════════════════════════════════════════" echo "" echo " Current: v$cur_version ($cur_docs documentation files)" - echo " Target: v$TARGET_VERSION ($TARGET_DOCS documentation files)" + echo " Target: v$TARGET_VERSION" echo "" echo " What's New in v$TARGET_VERSION:" - echo " • 2x documentation coverage ($TARGET_DOCS files)" + echo " • 2x documentation coverage" echo " • Domain-based filename convention (claude-code__*.md)" echo " • Modular Python packages (fetcher/, lookup/)" echo " • Safety thresholds for sync protection" - echo " • 573 paths tracked across 6 categories" + echo " • Paths tracked across 6 categories" echo "" echo "════════════════════════════════════════════════════════════════" echo "" @@ -656,7 +656,7 @@ if [[ ! -d "$INSTALL_DIR/docs" ]]; then else DOC_COUNT=$(find "$INSTALL_DIR/docs" -name "*.md" 2>/dev/null | wc -l | tr -d ' ') if [[ "$DOC_COUNT" -lt 100 ]]; then - echo " ⚠️ Only $DOC_COUNT documentation files found (expected ~571)" + echo " ⚠️ Only $DOC_COUNT documentation files found (expected 250+)" else echo " ✓ Documentation files installed ($DOC_COUNT files)" fi @@ -690,6 +690,9 @@ echo "" echo "✅ Claude Code Docs v$TARGET_VERSION installed successfully!" echo "" +# Set TARGET_DOCS from the doc count computed during verification +TARGET_DOCS="${DOC_COUNT:-0}" + # Show upgrade summary if this was an upgrade IFS='|' read -r prev_version prev_docs prev_packages <<< "$CURRENT_VERSION_INFO" if [[ "$prev_version" != "none" && "$prev_version" != "$TARGET_VERSION" ]]; then @@ -719,9 +722,9 @@ echo "" echo "🔄 Updates: Run '/docs -t' to check for and pull latest documentation" echo "" -# Show what's installed (573 paths tracked in manifest across 6 categories) +# Show what's installed echo "📦 Installed Components:" -echo " • 573 documentation paths tracked (6 categories)" +echo " • Documentation paths tracked (6 categories)" echo " • AI-powered /docs command" echo "" @@ -766,7 +769,7 @@ else echo " • Enhanced AI routing capabilities" echo "" echo "Without Python, you can:" - echo " • Read all 573 documentation paths via /docs command" + echo " • Read all documentation paths via /docs command" echo " • Use AI-powered semantic queries" echo " • Check documentation freshness" echo " • View recent changes" diff --git a/scripts/claude-docs-helper.sh b/scripts/claude-docs-helper.sh index f58eebadd..ba290f264 100755 --- a/scripts/claude-docs-helper.sh +++ b/scripts/claude-docs-helper.sh @@ -6,7 +6,7 @@ set -euo pipefail # Installation path: ~/.claude-code-docs/scripts/claude-docs-helper.sh # Script version -ENHANCED_VERSION="0.5.0" +ENHANCED_VERSION="0.5.1" # Fixed installation path DOCS_PATH="$HOME/.claude-code-docs" @@ -152,10 +152,12 @@ enhanced_search() { fi local query="$*" - echo "🔍 Searching 573 documentation paths for: $query" + local path_count + path_count=$(python3 -c "import json; data=json.load(open('$DOCS_PATH/paths_manifest.json')); print(data['metadata'].get('total_paths', 'all'))" 2>/dev/null || echo "all") + echo "🔍 Searching $path_count documentation paths for: $query" echo "" - if python3 "$SCRIPTS_PATH/lookup_paths.py" "$query" 2>/dev/null; then + if (cd "$DOCS_PATH" && python3 "$SCRIPTS_PATH/lookup_paths.py" "$query") 2>/dev/null; then echo "" echo "💡 Tip: Use '/docs ' to read a specific document" else @@ -180,7 +182,7 @@ search_content() { echo "📖 Searching documentation content for: $query" echo "" - if python3 "$SCRIPTS_PATH/lookup_paths.py" --search-content "$query" 2>/dev/null; then + if (cd "$DOCS_PATH" && python3 "$SCRIPTS_PATH/lookup_paths.py" --search-content "$query") 2>/dev/null; then echo "" echo "💡 Tip: Use '/docs ' to read the full document" else @@ -202,7 +204,7 @@ validate_paths() { echo "This may take 30-60 seconds..." echo "" - if python3 "$SCRIPTS_PATH/lookup_paths.py" --validate-all 2>/dev/null; then + if (cd "$DOCS_PATH" && python3 "$SCRIPTS_PATH/lookup_paths.py" --validate-all) 2>/dev/null; then echo "" echo "✅ Validation complete" else @@ -211,7 +213,7 @@ validate_paths() { fi } -# Update all documentation (fetch all 573 paths) +# Update all documentation update_all_docs() { if ! check_enhanced_available; then echo "❌ Enhanced update not available" @@ -222,18 +224,20 @@ update_all_docs() { return fi - echo "🔄 Updating all documentation (573 paths)..." + local path_count + path_count=$(python3 -c "import json; data=json.load(open('$DOCS_PATH/paths_manifest.json')); print(data['metadata'].get('total_paths', 'all'))" 2>/dev/null || echo "all") + echo "🔄 Updating all documentation ($path_count paths)..." echo "This may take 2-3 minutes..." echo "" - if python3 "$SCRIPTS_PATH/main.py" --update-all 2>/dev/null; then + if (cd "$DOCS_PATH" && python3 "$SCRIPTS_PATH/main.py" --update-all) 2>/dev/null; then echo "" echo "✅ Documentation updated successfully" # Rebuild search index if available if [[ -f "$SCRIPTS_PATH/build_search_index.py" ]]; then echo "Rebuilding search index..." - python3 "$SCRIPTS_PATH/build_search_index.py" >/dev/null 2>&1 || true + (cd "$DOCS_PATH" && python3 "$SCRIPTS_PATH/build_search_index.py") >/dev/null 2>&1 || true fi else echo "⚠️ Enhanced update failed" @@ -251,12 +255,12 @@ show_enhanced_help() { echo "─────────────────────────────────────────────────────────────────" echo "" echo "Search & Discovery:" - echo " --search Fuzzy search across 573 documentation paths" + echo " --search Fuzzy search across documentation paths" echo " --search-content Full-text content search across all documentation" echo "" echo "Maintenance:" echo " --validate Validate all paths (check for 404s)" - echo " --update-all Fetch all 573 documentation pages" + echo " --update-all Fetch all documentation pages" echo "" echo "Status:" echo " --version Show version information" @@ -307,7 +311,9 @@ show_version() { echo " ✅ Path validation: Available" else echo " ❌ Enhanced features: DISABLED" - echo " (571 documentation files available, Python features require Python 3.9+)" + local doc_count + doc_count=$(find "$DOCS_PATH/docs" -name "*.md" 2>/dev/null | wc -l | tr -d ' ') + echo " ($doc_count documentation files available, Python features require Python 3.9+)" fi echo "" } diff --git a/scripts/lookup/search.py b/scripts/lookup/search.py index c48b05f84..99a70ce19 100644 --- a/scripts/lookup/search.py +++ b/scripts/lookup/search.py @@ -130,7 +130,9 @@ def create_enriched_search_results( @lru_cache(maxsize=1) def load_search_index() -> Optional[Dict]: """Load full-text search index (cached).""" - index_file = Path("docs/.search_index.json") + # Resolve path relative to the repo root (two levels up from this file) + repo_root = Path(__file__).resolve().parent.parent.parent + index_file = repo_root / "docs" / ".search_index.json" if not index_file.exists(): return None diff --git a/tests/integration/test_github_actions.py b/tests/integration/test_github_actions.py index de8f1c51a..263d6d911 100644 --- a/tests/integration/test_github_actions.py +++ b/tests/integration/test_github_actions.py @@ -1,6 +1,7 @@ """Integration tests for GitHub Actions workflow simulation.""" import pytest +import re import sys from pathlib import Path import json @@ -140,6 +141,79 @@ def test_scripts_are_executable(self, project_root): assert script.is_file() +class TestManifestStaging: + """Test that CI/CD stages all required files.""" + + @pytest.mark.integration + def test_workflow_stages_paths_manifest(self, project_root): + """Test that update-docs workflow stages paths_manifest.json (not just docs/).""" + workflow_file = project_root / ".github" / "workflows" / "update-docs.yml" + content = workflow_file.read_text() + + # The git add command must include paths_manifest.json + # It should NOT be just "git add -A docs/" + assert 'paths_manifest.json' in content, ( + "Workflow must stage paths_manifest.json — currently only stages docs/" + ) + + +class TestSearchIndexGeneration: + """Test that CI/CD generates search index.""" + + @pytest.mark.integration + def test_workflow_builds_search_index(self, project_root): + """Test that update-docs workflow runs build_search_index.py.""" + workflow_file = project_root / ".github" / "workflows" / "update-docs.yml" + content = workflow_file.read_text() + + assert 'build_search_index.py' in content, ( + "Workflow must run build_search_index.py to generate .search_index.json" + ) + + @pytest.mark.integration + def test_build_search_index_script_exists(self, project_root): + """Test that the search index builder script exists.""" + script = project_root / "scripts" / "build_search_index.py" + assert script.exists(), "scripts/build_search_index.py must exist" + + +class TestHelperScriptPythonCalls: + """Test that helper script Python calls use correct working directory.""" + + @pytest.mark.integration + def test_python_calls_use_subshell_cd(self, project_root): + """Test Python calls are wrapped with cd to repo root.""" + helper = project_root / "scripts" / "claude-docs-helper.sh" + content = helper.read_text() + + python_calls = [ + line.strip() for line in content.splitlines() + if 'python3' in line + and not line.strip().startswith('#') + and 'lookup_paths.py' in line + ] + + for call in python_calls: + # Each call should use (cd ... && python3 ...) subshell pattern + assert re.search(r'\(cd\s+', call), ( + f"Python call must use '(cd ...' subshell pattern: {call}" + ) + + @pytest.mark.integration + def test_helper_no_hardcoded_path_counts(self, project_root): + """Test helper script doesn't contain hardcoded path counts.""" + helper = project_root / "scripts" / "claude-docs-helper.sh" + content = helper.read_text() + + # Should not hardcode specific numbers of paths + assert 'Searching 573' not in content, ( + "Helper script must not hardcode '573' doc count" + ) + assert 'fetch all 573' not in content.lower(), ( + "Helper script must not hardcode '573' doc count" + ) + + class TestWorkflowOutputs: """Test workflow outputs and artifacts.""" diff --git a/tests/unit/test_lookup_functions.py b/tests/unit/test_lookup_functions.py index 7159aed15..7c79fbf68 100644 --- a/tests/unit/test_lookup_functions.py +++ b/tests/unit/test_lookup_functions.py @@ -369,3 +369,48 @@ def test_format_content_result_with_preview(self): output = format_content_result(result, 1) assert "test preview" in output + + +class TestLoadSearchIndex: + """Test search index loading with various working directories.""" + + def test_load_search_index_uses_script_relative_path(self): + """Test that load_search_index resolves path relative to repo root, not cwd.""" + from lookup.search import load_search_index + import inspect + + # The function should use a path relative to the script's location, + # not the current working directory. + # Use inspect.getsource directly — works whether or not the function + # is decorated (avoids fragile __wrapped__ access). + source = inspect.getsource(load_search_index) + + # Should NOT use a bare relative path like Path("docs/.search_index.json") + # Should reference __file__ or an absolute path calculation + assert 'Path("docs/.search_index.json")' not in source, ( + "load_search_index must not use bare relative path — " + "fails when called from different working directory (issue #15)" + ) + + def test_load_search_index_works_from_different_cwd(self, tmp_path, monkeypatch): + """load_search_index should work regardless of current working directory. + + Before the __file__-relative fix (issue #15), this function used a bare + relative path and would fail or return None when cwd != repo root. + After the fix, it resolves from __file__ and returns the real index + regardless of cwd. + """ + monkeypatch.chdir(tmp_path) # cwd has no docs/ directory + + from lookup.search import load_search_index + load_search_index.cache_clear() + + # Should not crash — the __file__-relative path resolves to the real + # repo root index even when cwd is elsewhere + result = load_search_index() + + # If the index file exists in the repo, we get data back (not None); + # if it doesn't exist (e.g., fresh clone), we get None gracefully. + # Either way, no crash. + if result is not None: + assert "index" in result, "Returned data should contain 'index' key" diff --git a/tests/unit/test_manifest_validation.py b/tests/unit/test_manifest_validation.py index 94f27e97f..051069a19 100644 --- a/tests/unit/test_manifest_validation.py +++ b/tests/unit/test_manifest_validation.py @@ -8,8 +8,14 @@ """ import pytest import json +import sys from pathlib import Path +# Add scripts directory to path for access to fetcher/lookup packages +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts")) + +from fetcher.paths import url_to_safe_filename + @pytest.fixture def project_root(): """Path to project root""" @@ -38,21 +44,54 @@ def broken_paths(project_root): return json.load(f) return {} +@pytest.fixture +def docs_files_on_disk(project_root): + """Get set of actual documentation files on disk.""" + docs_dir = project_root / 'docs' + return set(f.name for f in docs_dir.glob('*.md')) + class TestPathsManifest: """Tests for paths_manifest.json""" - def test_no_deprecated_paths(self, paths_manifest, broken_paths): - """Ensure manifest doesn't contain deprecated paths""" - if not broken_paths: - pytest.skip("broken_paths_categorized.json not available") - - deprecated = set(broken_paths.get('deprecated_paths', [])) - - # Check all categories + def test_no_deprecated_paths(self, paths_manifest, broken_paths, docs_files_on_disk): + """Ensure manifest doesn't contain deprecated paths. + + Validates two ways: + 1. Against broken_paths_categorized.json if available (explicit deprecated list) + 2. Against actual files on disk — paths in manifest should have corresponding + doc files. Paths without files are likely deprecated or unfetchable. + """ + # Method 1: Check against explicit deprecated list if available + if broken_paths: + deprecated = set(broken_paths.get('deprecated_paths', [])) + for category, paths in paths_manifest['categories'].items(): + for path in paths: + assert path not in deprecated, \ + f"Deprecated path found: {path} in {category}" + + # Method 2: Check that manifest paths have corresponding files on disk. + # Uses url_to_safe_filename() from fetcher.paths — the same production + # function used to generate filenames during doc fetching. + orphaned_paths = [] for category, paths in paths_manifest['categories'].items(): for path in paths: - assert path not in deprecated, \ - f"Deprecated path found: {path} in {category}" + expected_file = url_to_safe_filename(path) + + if expected_file not in docs_files_on_disk: + orphaned_paths.append((category, path, expected_file)) + + # Allow tolerance — some paths are unfetchable (HTML-only, redirects, SDK + # language variants). But more than 20% orphaned indicates manifest drift. + total_paths = sum(len(p) for p in paths_manifest['categories'].values()) + orphan_pct = (len(orphaned_paths) / total_paths * 100) if total_paths > 0 else 0 + + assert orphan_pct < 20, ( + f"{len(orphaned_paths)} of {total_paths} manifest paths ({orphan_pct:.1f}%) " + f"have no corresponding doc file on disk. " + f"First 10 orphans:\n" + + "\n".join(f" [{cat}] {path} (expected: {f})" + for cat, path, f in orphaned_paths[:10]) + ) def test_metadata_accuracy(self, paths_manifest): """Ensure metadata reflects actual content""" diff --git a/tests/validation/test_link_integrity.py b/tests/validation/test_link_integrity.py index 97091ad9e..2adbb28ef 100644 --- a/tests/validation/test_link_integrity.py +++ b/tests/validation/test_link_integrity.py @@ -29,26 +29,77 @@ def test_find_internal_links(self, sample_markdown): assert len(internal_links) > 0 @pytest.mark.integration - def test_internal_links_in_manifest(self, sample_markdown, paths_manifest): - """Test internal links point to paths in manifest.""" - links = re.findall(r'\[([^\]]+)\]\((/en/[^)#]+)', sample_markdown) + def test_internal_links_in_manifest(self, paths_manifest, project_root): + """Test internal links in actual docs point to paths in manifest. - if not links: - pytest.skip("No internal links in sample") + Scans all documentation files for internal links (markdown link syntax) + and validates that a reasonable percentage resolve to known manifest paths. - # Get all valid paths - all_paths = [] + Two link patterns exist in the docs: + - /docs/en/... (platform docs linking to each other) + - /en/... (Claude Code docs using short paths) + """ + docs_dir = project_root / 'docs' + + # Build set of all known manifest paths (stripped of leading /) + all_paths = set() for category_paths in paths_manifest['categories'].values(): - all_paths.extend(category_paths) + for p in category_paths: + all_paths.add(p.strip('/')) + + # Scan real docs for internal links + total_links = 0 + broken_links = [] + + for md_file in sorted(docs_dir.glob('*.md')): + try: + content = md_file.read_text(encoding='utf-8', errors='ignore') + except Exception: + continue + + # Match markdown links: [text](/docs/en/...) or [text](/en/...) + links = re.findall( + r'\[([^\]]+)\]\((/(?:docs/)?en/[^)#\s]+)', + content + ) + + for text, url in links: + total_links += 1 + clean_path = url.strip('/') + + # Try exact match first + if clean_path in all_paths: + continue + + # Claude Code docs use /en/ which maps to /docs/en/ + # in the manifest + if clean_path.startswith('en/') and f'docs/{clean_path}' in all_paths: + continue + + broken_links.append((md_file.name, text, url)) + + # Our docs corpus contains thousands of internal links; fewer than 100 + # signals broken regex extraction or missing doc files, not sparse linking. + assert total_links > 100, ( + f"Expected 100+ internal links in docs, found {total_links}. " + f"Link extraction may be broken." + ) - # Check each link - for text, url in links: - # Link should be in manifest (or be a valid path) - # Note: Some links may point to anchors within valid pages - base_path = url.split('#')[0] # Remove fragment + # Allow tolerance — some links point to pages that aren't in our manifest + # (e.g., pages only on code.claude.com, dynamically generated pages, or + # paths that use different naming than our sitemap discovers) + broken_pct = (len(broken_links) / total_links * 100) if total_links else 0 - # This test may need to be lenient for dynamically generated content - pass + assert broken_pct < 30, ( + f"{len(broken_links)} of {total_links} internal links ({broken_pct:.1f}%) " + f"don't resolve to manifest paths. First 10:\n" + + "\n".join(f" [{f}] '{t}' -> {u}" + for f, t, u in broken_links[:10]) + ) + + # Report link statistics for CI log visibility + print(f"\n Link check passed: {total_links} total links, " + f"{len(broken_links)} unresolved ({broken_pct:.1f}%)") @pytest.mark.integration def test_relative_links_resolved(self):