> ## Documentation Index
> Fetch the complete documentation index at: https://docs.factory.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Documentation Sync

> Keep documentation in sync with code changes automatically

This cookbook shows how to automatically update documentation when code changes, ensure docs stay current, and generate API documentation from code.

## How it works

Documentation sync hooks can:

1. **Update API docs**: Generate documentation from code annotations
2. **Sync code examples**: Keep examples in docs matching actual code
3. **Validate doc references**: Ensure docs reference correct code paths
4. **Update changelogs**: Auto-generate changelog entries
5. **Check doc completeness**: Require documentation for new features

## Prerequisites

Install documentation tools:

<CodeGroup>
  ```bash TypeScript/JavaScript theme={null}
  npm install -D typedoc jsdoc documentation
  npm install -D @readme/openapi-parser  # For OpenAPI
  ```

  ```bash Python theme={null}
  pip install sphinx pdoc3 mkdocs
  pip install pydoc-markdown  # Markdown docs from docstrings
  ```

  ```bash Go theme={null}
  go install golang.org/x/tools/cmd/godoc@latest
  go install github.com/swaggo/swag/cmd/swag@latest  # For Swagger
  ```
</CodeGroup>

## Basic documentation automation

### Auto-generate API docs

Generate API documentation when code changes.

Create `.factory/hooks/generate-api-docs.sh`:

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Only process code files
if ! echo "$file_path" | grep -qE '\.(ts|tsx|js|jsx|py|go)$'; then
  exit 0
fi

# Skip test files
if echo "$file_path" | grep -qE '\.(test|spec)\.(ts|tsx|js|jsx)$'; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

echo "📚 Updating API documentation..."

case "$file_path" in
  *.ts|*.tsx)
    # TypeScript - use typedoc
    if command -v typedoc &> /dev/null && [ -f "typedoc.json" ]; then
      echo "Generating TypeScript docs..."
      typedoc --out docs/api src/ 2>&1 || {
        echo "⚠️ Failed to generate docs" >&2
      }
      echo "✓ API docs updated at docs/api"
    fi
    ;;
    
  *.py)
    # Python - use pdoc
    if command -v pdoc &> /dev/null; then
      module_name=$(echo "$file_path" | sed 's|^src/||; s|/|.|g; s|\.py$||')
      echo "Generating Python docs for $module_name..."
      
      pdoc --html --output-dir docs/api "$module_name" --force 2>&1 || {
        echo "⚠️ Failed to generate docs" >&2
      }
      echo "✓ API docs updated"
    fi
    ;;
    
  *.go)
    # Go - use godoc
    if command -v godoc &> /dev/null; then
      echo "Generating Go docs..."
      # Go docs are typically served, not generated
      # But we can create markdown from godoc
      echo "✓ Go docs available via 'godoc -http=:6060'"
    fi
    ;;
esac

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/generate-api-docs.sh
```

Add to `.factory/settings.json`:

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Create|Edit|ApplyPatch",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/generate-api-docs.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

### Update README on project changes

Keep README in sync with project structure.

Create `.factory/hooks/update-readme.sh`:

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Only update on package.json or structure changes
if ! echo "$file_path" | grep -qE '(package\.json|README\.md)$'; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Don't run if README is the file being edited
if [ "$file_path" = "README.md" ]; then
  exit 0
fi

echo "📝 Checking README..."

# Update package info in README
if [ -f "package.json" ] && [ -f "README.md" ]; then
  name=$(jq -r '.name' package.json)
  version=$(jq -r '.version' package.json)
  description=$(jq -r '.description' package.json)
  
  # Check if version in README matches package.json
  if ! grep -q "Version: $version" README.md; then
    echo "⚠️ README version out of sync with package.json"
    echo "Package version: $version"
    echo "Consider updating README.md or ask me to do it"
  fi
fi

# Check for missing documentation sections
required_sections=("Installation" "Usage" "API" "Contributing")
missing_sections=()

for section in "${required_sections[@]}"; do
  if ! grep -qi "## $section" README.md; then
    missing_sections+=("$section")
  fi
done

if [ ${#missing_sections[@]} -gt 0 ]; then
  echo "⚠️ README missing sections:"
  printf '  - %s\n' "${missing_sections[@]}"
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/update-readme.sh
```

### Sync code examples in documentation

Ensure code examples in docs match actual code:

Create `.factory/hooks/sync-doc-examples.py`:

````python theme={null}
#!/usr/bin/env python3
"""
Sync code examples in markdown docs with actual source code.
"""
import json
import sys
import re
import os

def extract_code_snippets(doc_file):
    """Extract code snippets from markdown file."""
    with open(doc_file, 'r') as f:
        content = f.read()
    
    # Find code blocks with source file annotations
    # Format: ```typescript
    # // From: src/components/Button.tsx
    pattern = r'```(\w+)\n// From: (.*?)\n(.*?)\n```'
    snippets = re.findall(pattern, content, re.DOTALL)
    
    return snippets

def verify_snippet_matches_source(language, source_file, snippet):
    """Check if snippet exists in source file."""
    if not os.path.exists(source_file):
        return False, f"Source file not found: {source_file}"
    
    with open(source_file, 'r') as f:
        source_content = f.read()
    
    # Normalize whitespace for comparison
    normalized_snippet = ' '.join(snippet.split())
    normalized_source = ' '.join(source_content.split())
    
    if normalized_snippet in normalized_source:
        return True, "Snippet matches source"
    else:
        return False, "Snippet does not match source code"

def main():
    input_data = json.load(sys.stdin)
    file_path = input_data.get('tool_input', {}).get('file_path', '')
    
    # Check both code files and doc files
    if file_path.endswith(('.md', '.mdx')):
        # Doc file changed - verify all examples
        print(f"📖 Verifying code examples in {file_path}...")
        
        snippets = extract_code_snippets(file_path)
        issues = []
        
        for lang, source, snippet in snippets:
            matches, message = verify_snippet_matches_source(lang, source, snippet)
            if not matches:
                issues.append(f"{source}: {message}")
        
        if issues:
            print("⚠️ Some code examples may be outdated:", file=sys.stderr)
            for issue in issues:
                print(f"  - {issue}", file=sys.stderr)
            print("\nConsider updating the examples in the documentation.", file=sys.stderr)
        else:
            print("✓ All code examples are in sync")
    
    elif file_path.endswith(('.ts', '.tsx', '.js', '.jsx', '.py')):
        # Code file changed - check if it's referenced in docs
        print(f"Checking if {file_path} is referenced in documentation...")
        
        # Find docs that reference this file
        doc_files = []
        for root, dirs, files in os.walk('docs'):
            for file in files:
                if file.endswith(('.md', '.mdx')):
                    doc_path = os.path.join(root, file)
                    with open(doc_path, 'r') as f:
                        if file_path in f.read():
                            doc_files.append(doc_path)
        
        if doc_files:
            print(f"ℹ️ File is referenced in {len(doc_files)} documentation file(s):")
            for doc in doc_files:
                print(f"  - {doc}")
            print("\nConsider updating these docs if the API changed.")
    
    sys.exit(0)

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(0)
````

```bash theme={null}
chmod +x .factory/hooks/sync-doc-examples.py
```

## Advanced documentation automation

### OpenAPI/Swagger spec validation

Ensure API documentation matches implementation:

Create `.factory/hooks/validate-openapi.sh`:

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Check if this is an API route file
if ! echo "$file_path" | grep -qE 'routes/.*\.(ts|js)$'; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Check if OpenAPI spec exists
if [ ! -f "openapi.yaml" ] && [ ! -f "swagger.yaml" ]; then
  exit 0
fi

spec_file="openapi.yaml"
[ -f "swagger.yaml" ] && spec_file="swagger.yaml"

echo "🔍 Validating OpenAPI spec..."

# Extract route definitions from code
# This is simplified - you'd need a more sophisticated parser
routes=$(grep -E "router\.(get|post|put|delete|patch)" "$file_path" | \
  sed -E "s/.*router\.([a-z]+)\(['\"](.*?)['\"].*/\1 \2/" || true)

if [ -n "$routes" ]; then
  echo "Routes in $file_path:"
  echo "$routes" | sed 's/^/  /'
  
  # Check if routes are documented in OpenAPI spec
  while IFS= read -r route; do
    method=$(echo "$route" | awk '{print $1}')
    path=$(echo "$route" | awk '{print $2}')
    
    if ! grep -q "$path" "$spec_file"; then
      echo "⚠️ Route not documented in $spec_file: $method $path"
      echo "Consider adding this endpoint to the API documentation."
    fi
  done <<< "$routes"
fi

# Validate spec syntax
if command -v swagger-cli &> /dev/null; then
  if swagger-cli validate "$spec_file" 2>&1; then
    echo "✓ OpenAPI spec is valid"
  else
    echo "❌ OpenAPI spec has errors" >&2
  fi
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/validate-openapi.sh
```

### JSDoc/TSDoc enforcement

Require documentation comments for public APIs:

Create `.factory/hooks/enforce-jsdoc.sh`:

```bash theme={null}
#!/bin/bash

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
content=$(echo "$input" | jq -r '.tool_input.content // ""')

# Only check TypeScript/JavaScript files
if ! echo "$file_path" | grep -qE '\.(ts|tsx|js|jsx)$'; then
  exit 0
fi

# Skip test files
if echo "$file_path" | grep -qE '\.(test|spec)\.(ts|tsx|js|jsx)$'; then
  exit 0
fi

# Check for exported functions/classes without JSDoc
missing_docs=()

# Find exported functions without JSDoc
while IFS= read -r line; do
  # Check if line is an export
  if echo "$line" | grep -qE '^export (function|class|const|interface|type)'; then
    # Get the name
    name=$(echo "$line" | sed -E 's/^export (function|class|const|interface|type) ([a-zA-Z0-9_]+).*/\2/')
    
    # Check if there's a JSDoc comment before it
    # This is simplified - you'd want a proper parser
    if ! echo "$content" | grep -B1 "^export.*$name" | grep -qE '^\s*\*'; then
      missing_docs+=("$name")
    fi
  fi
done <<< "$content"

if [ ${#missing_docs[@]} -gt 0 ]; then
  echo "⚠️ Exported items missing documentation:" >&2
  printf '  - %s\n' "${missing_docs[@]}" >&2
  echo "" >&2
  echo "Please add JSDoc comments for public APIs:" >&2
  echo "/**" >&2
  echo " * Description of what this does" >&2
  echo " * @param paramName - Parameter description" >&2
  echo " * @returns Return value description" >&2
  echo " */" >&2
  
  # Warning only, don't block
  # Change to exit 2 to enforce documentation
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/enforce-jsdoc.sh
```

### Generate changelog from commits

Automatically build changelog from git history:

Create `.factory/hooks/generate-changelog.sh`:

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
hook_event=$(echo "$input" | jq -r '.hook_event_name')

# Only run on Stop (after work is complete)
if [ "$hook_event" != "Stop" ]; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Check if there are new commits since last changelog update
if [ ! -f "CHANGELOG.md" ]; then
  exit 0
fi

# Get last version in changelog
last_version=$(grep -m1 "## \[" CHANGELOG.md | sed -E 's/.*\[([0-9.]+)\].*/\1/')

if [ -z "$last_version" ]; then
  exit 0
fi

# Get commits since last version tag
if git rev-parse "v$last_version" &>/dev/null; then
  new_commits=$(git log "v$last_version..HEAD" --oneline)
  
  if [ -n "$new_commits" ]; then
    echo "📝 New commits since v$last_version"
    echo ""
    echo "Consider updating CHANGELOG.md with:"
    echo ""
    
    # Group commits by type
    echo "### Features"
    git log "v$last_version..HEAD" --oneline | grep "^[a-f0-9]* feat" | sed 's/^[a-f0-9]* feat[:(]/- /' || true
    echo ""
    
    echo "### Bug Fixes"
    git log "v$last_version..HEAD" --oneline | grep "^[a-f0-9]* fix" | sed 's/^[a-f0-9]* fix[:(]/- /' || true
    echo ""
  fi
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/generate-changelog.sh
```

### Docs coverage report

Track which code has documentation:

Create `.factory/hooks/docs-coverage.py`:

```python theme={null}
#!/usr/bin/env python3
"""
Calculate documentation coverage for the codebase.
"""
import os
import re
import sys
import json

def count_functions(file_path):
    """Count functions in a file."""
    with open(file_path, 'r') as f:
        content = f.read()
    
    # Count function definitions (simplified)
    if file_path.endswith('.py'):
        functions = re.findall(r'^def \w+\(', content, re.MULTILINE)
    elif file_path.endswith(('.ts', '.tsx', '.js', '.jsx')):
        functions = re.findall(r'(^export function \w+|^function \w+|const \w+ = \(.*\) =>)', 
                              content, re.MULTILINE)
    else:
        return 0
    
    return len(functions)

def count_documented_functions(file_path):
    """Count functions with documentation."""
    with open(file_path, 'r') as f:
        content = f.read()
    
    if file_path.endswith('.py'):
        # Look for docstrings
        pattern = r'def \w+\(.*?\):\s*"""'
    elif file_path.endswith(('.ts', '.tsx', '.js', '.jsx')):
        # Look for JSDoc comments
        pattern = r'/\*\*.*?\*/\s*(export )?function \w+'
    else:
        return 0
    
    documented = re.findall(pattern, content, re.DOTALL)
    return len(documented)

def main():
    # Calculate documentation coverage for src/
    src_dir = 'src'
    
    if not os.path.exists(src_dir):
        sys.exit(0)
    
    total_functions = 0
    documented_functions = 0
    
    for root, dirs, files in os.walk(src_dir):
        # Skip test files
        dirs[:] = [d for d in dirs if d not in ['__tests__', 'test', 'tests']]
        
        for file in files:
            if file.endswith(('.py', '.ts', '.tsx', '.js', '.jsx')):
                file_path = os.path.join(root, file)
                total_functions += count_functions(file_path)
                documented_functions += count_documented_functions(file_path)
    
    if total_functions > 0:
        coverage = (documented_functions / total_functions) * 100
        
        print(f"\n📊 Documentation Coverage Report")
        print(f"Documented functions: {documented_functions}/{total_functions}")
        print(f"Coverage: {coverage:.1f}%")
        
        if coverage < 60:
            print("\n⚠️ Documentation coverage is low")
            print("Consider adding documentation to public APIs")
        elif coverage < 80:
            print("\n✓ Documentation coverage is good, but could be better")
        else:
            print("\n✓ Excellent documentation coverage!")

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
    
    sys.exit(0)
```

```bash theme={null}
chmod +x .factory/hooks/docs-coverage.py
```

Run on session end:

```json theme={null}
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/docs-coverage.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

## Best practices

<Steps>
  <Step title="Automate but don't auto-commit">
    Generate docs but let users review before committing:

    ```bash theme={null}
    # Generate docs
    typedoc --out docs/api src/

    # Alert but don't commit
    echo "✓ API docs regenerated at docs/api"
    echo "Review changes: git diff docs/"
    ```
  </Step>

  <Step title="Keep doc examples testable">
    Extract examples from actual tests:

    ```bash theme={null}
    # Extract example from test file
    sed -n '/Example:/,/End example/p' Button.test.tsx > docs/examples/button.md
    ```
  </Step>

  <Step title="Version documentation">
    Tag docs with version info:

    ```markdown theme={null}
    # API Reference

    > Version: 2.1.0  
    > Last updated: 2024-01-15
    ```
  </Step>

  <Step title="Link docs to code">
    Include source file references:

    ```markdown theme={null}
    ## Button Component

    Source: [`src/components/Button.tsx`](../src/components/Button.tsx)
    ```
  </Step>

  <Step title="Use doc generation tools">
    Don't manually maintain API docs:

    ```bash theme={null}
    # TypeScript
    typedoc --out docs/api src/

    # Python
    pdoc --html --output-dir docs/api src/

    # Go
    godoc -http=:6060
    ```
  </Step>
</Steps>

## Troubleshooting

**Problem**: Can't generate documentation

**Solution**: Check tool configuration:

```bash theme={null}
# Verify tool installation
which typedoc

# Check config file
cat typedoc.json

# Test manually
typedoc --version
typedoc --help
```

**Problem**: Doc examples don't match code

**Solution**: Extract examples from tests:

```typescript theme={null}
// In test file
/* DOC_EXAMPLE_START: basic-usage */
const result = myFunction(input);
expect(result).toBe(expected);
/* DOC_EXAMPLE_END */
```

Then extract programmatically:

```bash theme={null}
sed -n '/DOC_EXAMPLE_START/,/DOC_EXAMPLE_END/p' test.ts
```

**Problem**: Doc generation takes too long

**Solution**: Build incrementally:

```bash theme={null}
# Only rebuild changed files
typedoc --incremental src/

# Or skip in hooks, run manually
if [ "$SKIP_DOC_BUILD" = "true" ]; then
  exit 0
fi
```

## See also

* [Hooks reference](/reference/hooks-reference) - Complete hooks API documentation
* [Get started with hooks](/cli/configuration/hooks-guide) - Basic hooks introduction
* [Code validation](/guides/hooks/code-validation) - Enforce code standards
* [Git workflows](/guides/hooks/git-workflows) - Git integration
