How it works
Documentation sync hooks can:- Update API docs: Generate documentation from code annotations
- Sync code examples: Keep examples in docs matching actual code
- Validate doc references: Ensure docs reference correct code paths
- Update changelogs: Auto-generate changelog entries
- Check doc completeness: Require documentation for new features
Prerequisites
Install documentation tools:Copy
Ask AI
npm install -D typedoc jsdoc documentation
npm install -D @readme/openapi-parser # For OpenAPI
Basic documentation automation
Auto-generate API docs
Generate API documentation when code changes. Create.factory/hooks/generate-api-docs.sh:
Copy
Ask AI
#!/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
Copy
Ask AI
chmod +x .factory/hooks/generate-api-docs.sh
.factory/settings.json:
Copy
Ask AI
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"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:
Copy
Ask AI
#!/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
Copy
Ask AI
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:
Copy
Ask AI
#!/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)
Copy
Ask AI
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:
Copy
Ask AI
#!/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
Copy
Ask AI
chmod +x .factory/hooks/validate-openapi.sh
JSDoc/TSDoc enforcement
Require documentation comments for public APIs: Create.factory/hooks/enforce-jsdoc.sh:
Copy
Ask AI
#!/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
Copy
Ask AI
chmod +x .factory/hooks/enforce-jsdoc.sh
Generate changelog from commits
Automatically build changelog from git history: Create.factory/hooks/generate-changelog.sh:
Copy
Ask AI
#!/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
Copy
Ask AI
chmod +x .factory/hooks/generate-changelog.sh
Docs coverage report
Track which code has documentation: Create.factory/hooks/docs-coverage.py:
Copy
Ask AI
#!/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)
Copy
Ask AI
chmod +x .factory/hooks/docs-coverage.py
Copy
Ask AI
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/docs-coverage.py",
"timeout": 10
}
]
}
]
}
}
Best practices
1
Automate but don't auto-commit
Generate docs but let users review before committing:
Copy
Ask AI
# 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/"
2
Keep doc examples testable
Extract examples from actual tests:
Copy
Ask AI
# Extract example from test file
sed -n '/Example:/,/End example/p' Button.test.tsx > docs/examples/button.md
3
Version documentation
Tag docs with version info:
Copy
Ask AI
# API Reference
> Version: 2.1.0
> Last updated: 2024-01-15
4
Link docs to code
Include source file references:
Copy
Ask AI
## Button Component
Source: [`src/components/Button.tsx`](../src/components/Button.tsx)
5
Use doc generation tools
Don’t manually maintain API docs:
Copy
Ask AI
# TypeScript
typedoc --out docs/api src/
# Python
pdoc --html --output-dir docs/api src/
# Go
godoc -http=:6060
Troubleshooting
Problem: Can’t generate documentation Solution: Check tool configuration:Copy
Ask AI
# Verify tool installation
which typedoc
# Check config file
cat typedoc.json
# Test manually
typedoc --version
typedoc --help
Copy
Ask AI
// In test file
/* DOC_EXAMPLE_START: basic-usage */
const result = myFunction(input);
expect(result).toBe(expected);
/* DOC_EXAMPLE_END */
Copy
Ask AI
sed -n '/DOC_EXAMPLE_START/,/DOC_EXAMPLE_END/p' test.ts
Copy
Ask AI
# 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 - Complete hooks API documentation
- Get started with hooks - Basic hooks introduction
- Code validation - Enforce code standards
- Git workflows - Git integration
