Loading...
Automated documentation coverage analysis with missing docstring detection, API documentation validation, and completeness scoring
{
"hookConfig": {
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/documentation-coverage-checker.sh",
"matchers": [
"write",
"edit"
]
}
}
},
"scriptContent": "#!/usr/bin/env bash\n\n# Read the tool input from stdin\nINPUT=$(cat)\nTOOL_NAME=$(echo \"$INPUT\" | jq -r '.tool_name')\nFILE_PATH=$(echo \"$INPUT\" | jq -r '.tool_input.file_path // .tool_input.path // \"\"')\n\nif [ -z \"$FILE_PATH\" ]; then\n exit 0\nfi\n\n# Configuration\nREPORT_FILE=\".claude/reports/docs-coverage-$(date +%Y%m%d).txt\"\nMIN_COVERAGE=${DOC_COVERAGE_THRESHOLD:-70}\n\nmkdir -p \"$(dirname \"$REPORT_FILE\")\"\n\n# Function to check if file needs documentation review\nneeds_doc_check() {\n local file=$1\n \n case \"$file\" in\n *.js|*.jsx|*.ts|*.tsx|*.py|*.go|*.rs|*.java|*.rb)\n return 0\n ;;\n *)\n return 1\n ;;\n esac\n}\n\n# Function to check JavaScript/TypeScript documentation\ncheck_js_ts_docs() {\n local file=$1\n \n echo \"📝 Checking JS/TS documentation: $file\" >&2\n \n # Count functions\n local total_functions=$(grep -cE \"^\\s*(export\\s+)?(async\\s+)?function\\s+\\w+|^\\s*const\\s+\\w+\\s*=\\s*(async\\s+)?\\(|^\\s*\\w+\\s*\\(.*\\)\\s*\\{\" \"$file\" 2>/dev/null || echo \"0\")\n \n # Count documented functions (with JSDoc /** */)\n local documented=$(grep -B1 -cE \"^\\s*\\/\\*\\*\" \"$file\" 2>/dev/null || echo \"0\")\n \n if [ \"$total_functions\" -gt 0 ]; then\n local coverage=$((documented * 100 / total_functions))\n \n echo \"\" >> \"$REPORT_FILE\"\n echo \"JavaScript/TypeScript Documentation - $file\" >> \"$REPORT_FILE\"\n echo \"Total functions: $total_functions\" >> \"$REPORT_FILE\"\n echo \"Documented: $documented\" >> \"$REPORT_FILE\"\n echo \"Coverage: ${coverage}%\" >> \"$REPORT_FILE\"\n \n if [ \"$coverage\" -lt \"$MIN_COVERAGE\" ]; then\n echo \"⚠️ Documentation coverage ${coverage}% below threshold ${MIN_COVERAGE}%\" >&2\n echo \"💡 Add JSDoc comments to exported functions\" >&2\n else\n echo \"✅ Documentation coverage: ${coverage}%\" >&2\n fi\n fi\n \n # Check for exported items without docs\n if grep -E \"^export (class|function|const|interface|type)\" \"$file\" >/dev/null 2>&1; then\n echo \"📦 Exported items detected - ensure public API is documented\" >&2\n fi\n}\n\n# Function to check Python documentation\ncheck_python_docs() {\n local file=$1\n \n echo \"🐍 Checking Python documentation: $file\" >&2\n \n # Use interrogate if available\n if command -v interrogate &> /dev/null; then\n echo \"\" >> \"$REPORT_FILE\"\n echo \"Python Docstring Coverage - $file\" >> \"$REPORT_FILE\"\n \n local coverage_output=$(interrogate -v \"$file\" 2>/dev/null)\n echo \"$coverage_output\" >> \"$REPORT_FILE\"\n \n # Extract coverage percentage\n local coverage=$(echo \"$coverage_output\" | grep -oE '[0-9]+\\.[0-9]+%' | head -1 | tr -d '%')\n \n if [ -n \"$coverage\" ]; then\n if (( $(echo \"$coverage < $MIN_COVERAGE\" | bc -l) )); then\n echo \"⚠️ Docstring coverage ${coverage}% below threshold ${MIN_COVERAGE}%\" >&2\n else\n echo \"✅ Docstring coverage: ${coverage}%\" >&2\n fi\n fi\n else\n # Manual check for docstrings\n local total_defs=$(grep -cE \"^\\s*def\\s+\\w+|^\\s*class\\s+\\w+\" \"$file\" 2>/dev/null || echo \"0\")\n local documented=$(grep -A1 -cE \"^\\s*def\\s+\\w+|^\\s*class\\s+\\w+\" \"$file\" | grep -c '\"\"\"' || echo \"0\")\n \n if [ \"$total_defs\" -gt 0 ]; then\n local coverage=$((documented * 100 / total_defs))\n echo \"⚠️ Estimated docstring coverage: ${coverage}%\" >&2\n echo \"💡 Install interrogate for accurate analysis: pip install interrogate\" >&2\n fi\n fi\n}\n\n# Function to check Go documentation\ncheck_go_docs() {\n local file=$1\n \n echo \"🐹 Checking Go documentation: $file\" >&2\n \n if command -v go &> /dev/null; then\n # Use go doc if available\n if go doc -all 2>/dev/null | grep -q \"$file\"; then\n echo \"✅ Go documentation present\" >&2\n else\n echo \"💡 Add godoc comments to exported functions/types\" >&2\n fi\n fi\n \n # Check for exported items without comments\n local undocumented=$(grep -E \"^func [A-Z]|^type [A-Z]\" \"$file\" | \\\n while read -r line; do\n grep -B1 \"$line\" \"$file\" | head -1 | grep -q \"^//\" || echo \"$line\"\n done | wc -l)\n \n if [ \"$undocumented\" -gt 0 ]; then\n echo \"⚠️ Found $undocumented undocumented exported items\" >&2\n fi\n}\n\n# Function to check README freshness\ncheck_readme_freshness() {\n if [ -f \"README.md\" ]; then\n local readme_age=$(($(date +%s) - $(stat -f%m \"README.md\" 2>/dev/null || stat -c%Y \"README.md\" 2>/dev/null || echo \"0\")))\n local days_old=$((readme_age / 86400))\n \n if [ \"$days_old\" -gt 90 ]; then\n echo \"📋 README.md is $days_old days old - consider updating\" >&2\n fi\n else\n echo \"⚠️ No README.md found - create project documentation\" >&2\n fi\n}\n\n# Function to check API documentation\ncheck_api_docs() {\n local file=$1\n \n # Check for API route definitions\n if grep -iE \"@(get|post|put|delete|patch)|router\\.(get|post|put|delete|patch)|app\\.(get|post|put|delete|patch)\" \"$file\" >/dev/null 2>&1; then\n echo \"🌐 API endpoint detected in: $file\" >&2\n \n # Check for OpenAPI/Swagger comments\n if ! grep -E \"@swagger|@openapi|@api\" \"$file\" >/dev/null 2>&1; then\n echo \"💡 Consider adding OpenAPI/Swagger documentation for API endpoints\" >&2\n fi\n \n # Check for request/response documentation\n if ! grep -E \"@param|@returns|@request|@response\" \"$file\" >/dev/null 2>&1; then\n echo \"💡 Document request parameters and response types\" >&2\n fi\n fi\n}\n\n# Main execution\nif needs_doc_check \"$FILE_PATH\"; then\n echo \"📚 Documentation check triggered: $FILE_PATH\" >&2\n \n # Language-specific checks\n case \"$FILE_PATH\" in\n *.js|*.jsx|*.ts|*.tsx)\n check_js_ts_docs \"$FILE_PATH\"\n check_api_docs \"$FILE_PATH\"\n ;;\n *.py)\n check_python_docs \"$FILE_PATH\"\n check_api_docs \"$FILE_PATH\"\n ;;\n *.go)\n check_go_docs \"$FILE_PATH\"\n ;;\n esac\n \n # General documentation checks\n check_readme_freshness\n \n # Documentation best practices\n echo \"\" >&2\n echo \"📖 Documentation Best Practices:\" >&2\n echo \" • Document all public APIs and exported functions\" >&2\n echo \" • Include parameter types and return values\" >&2\n echo \" • Add usage examples for complex functions\" >&2\n echo \" • Keep README.md up-to-date with recent changes\" >&2\n echo \" • Use consistent documentation format (JSDoc/TSDoc/etc)\" >&2\n \n if [ -s \"$REPORT_FILE\" ]; then\n echo \"\" >&2\n echo \"📄 Documentation report: $REPORT_FILE\" >&2\n fi\nelif [[ \"$FILE_PATH\" == *README* ]] || [[ \"$FILE_PATH\" == *CHANGELOG* ]]; then\n echo \"📝 Documentation file updated: $(basename \"$FILE_PATH\")\" >&2\n echo \"✅ Keep documentation current with code changes\" >&2\nfi\n\nexit 0"
}
.claude/hooks/
~/.claude/hooks/
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/documentation-coverage-checker.sh",
"matchers": [
"write",
"edit"
]
}
}
}
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Configuration
REPORT_FILE=".claude/reports/docs-coverage-$(date +%Y%m%d).txt"
MIN_COVERAGE=${DOC_COVERAGE_THRESHOLD:-70}
mkdir -p "$(dirname "$REPORT_FILE")"
# Function to check if file needs documentation review
needs_doc_check() {
local file=$1
case "$file" in
*.js|*.jsx|*.ts|*.tsx|*.py|*.go|*.rs|*.java|*.rb)
return 0
;;
*)
return 1
;;
esac
}
# Function to check JavaScript/TypeScript documentation
check_js_ts_docs() {
local file=$1
echo "📝 Checking JS/TS documentation: $file" >&2
# Count functions
local total_functions=$(grep -cE "^\s*(export\s+)?(async\s+)?function\s+\w+|^\s*const\s+\w+\s*=\s*(async\s+)?\(|^\s*\w+\s*\(.*\)\s*\{" "$file" 2>/dev/null || echo "0")
# Count documented functions (with JSDoc /** */)
local documented=$(grep -B1 -cE "^\s*\/\*\*" "$file" 2>/dev/null || echo "0")
if [ "$total_functions" -gt 0 ]; then
local coverage=$((documented * 100 / total_functions))
echo "" >> "$REPORT_FILE"
echo "JavaScript/TypeScript Documentation - $file" >> "$REPORT_FILE"
echo "Total functions: $total_functions" >> "$REPORT_FILE"
echo "Documented: $documented" >> "$REPORT_FILE"
echo "Coverage: ${coverage}%" >> "$REPORT_FILE"
if [ "$coverage" -lt "$MIN_COVERAGE" ]; then
echo "⚠️ Documentation coverage ${coverage}% below threshold ${MIN_COVERAGE}%" >&2
echo "💡 Add JSDoc comments to exported functions" >&2
else
echo "✅ Documentation coverage: ${coverage}%" >&2
fi
fi
# Check for exported items without docs
if grep -E "^export (class|function|const|interface|type)" "$file" >/dev/null 2>&1; then
echo "📦 Exported items detected - ensure public API is documented" >&2
fi
}
# Function to check Python documentation
check_python_docs() {
local file=$1
echo "🐍 Checking Python documentation: $file" >&2
# Use interrogate if available
if command -v interrogate &> /dev/null; then
echo "" >> "$REPORT_FILE"
echo "Python Docstring Coverage - $file" >> "$REPORT_FILE"
local coverage_output=$(interrogate -v "$file" 2>/dev/null)
echo "$coverage_output" >> "$REPORT_FILE"
# Extract coverage percentage
local coverage=$(echo "$coverage_output" | grep -oE '[0-9]+\.[0-9]+%' | head -1 | tr -d '%')
if [ -n "$coverage" ]; then
if (( $(echo "$coverage < $MIN_COVERAGE" | bc -l) )); then
echo "⚠️ Docstring coverage ${coverage}% below threshold ${MIN_COVERAGE}%" >&2
else
echo "✅ Docstring coverage: ${coverage}%" >&2
fi
fi
else
# Manual check for docstrings
local total_defs=$(grep -cE "^\s*def\s+\w+|^\s*class\s+\w+" "$file" 2>/dev/null || echo "0")
local documented=$(grep -A1 -cE "^\s*def\s+\w+|^\s*class\s+\w+" "$file" | grep -c '"""' || echo "0")
if [ "$total_defs" -gt 0 ]; then
local coverage=$((documented * 100 / total_defs))
echo "⚠️ Estimated docstring coverage: ${coverage}%" >&2
echo "💡 Install interrogate for accurate analysis: pip install interrogate" >&2
fi
fi
}
# Function to check Go documentation
check_go_docs() {
local file=$1
echo "🐹 Checking Go documentation: $file" >&2
if command -v go &> /dev/null; then
# Use go doc if available
if go doc -all 2>/dev/null | grep -q "$file"; then
echo "✅ Go documentation present" >&2
else
echo "💡 Add godoc comments to exported functions/types" >&2
fi
fi
# Check for exported items without comments
local undocumented=$(grep -E "^func [A-Z]|^type [A-Z]" "$file" | \
while read -r line; do
grep -B1 "$line" "$file" | head -1 | grep -q "^//" || echo "$line"
done | wc -l)
if [ "$undocumented" -gt 0 ]; then
echo "⚠️ Found $undocumented undocumented exported items" >&2
fi
}
# Function to check README freshness
check_readme_freshness() {
if [ -f "README.md" ]; then
local readme_age=$(($(date +%s) - $(stat -f%m "README.md" 2>/dev/null || stat -c%Y "README.md" 2>/dev/null || echo "0")))
local days_old=$((readme_age / 86400))
if [ "$days_old" -gt 90 ]; then
echo "📋 README.md is $days_old days old - consider updating" >&2
fi
else
echo "⚠️ No README.md found - create project documentation" >&2
fi
}
# Function to check API documentation
check_api_docs() {
local file=$1
# Check for API route definitions
if grep -iE "@(get|post|put|delete|patch)|router\.(get|post|put|delete|patch)|app\.(get|post|put|delete|patch)" "$file" >/dev/null 2>&1; then
echo "🌐 API endpoint detected in: $file" >&2
# Check for OpenAPI/Swagger comments
if ! grep -E "@swagger|@openapi|@api" "$file" >/dev/null 2>&1; then
echo "💡 Consider adding OpenAPI/Swagger documentation for API endpoints" >&2
fi
# Check for request/response documentation
if ! grep -E "@param|@returns|@request|@response" "$file" >/dev/null 2>&1; then
echo "💡 Document request parameters and response types" >&2
fi
fi
}
# Main execution
if needs_doc_check "$FILE_PATH"; then
echo "📚 Documentation check triggered: $FILE_PATH" >&2
# Language-specific checks
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx)
check_js_ts_docs "$FILE_PATH"
check_api_docs "$FILE_PATH"
;;
*.py)
check_python_docs "$FILE_PATH"
check_api_docs "$FILE_PATH"
;;
*.go)
check_go_docs "$FILE_PATH"
;;
esac
# General documentation checks
check_readme_freshness
# Documentation best practices
echo "" >&2
echo "📖 Documentation Best Practices:" >&2
echo " • Document all public APIs and exported functions" >&2
echo " • Include parameter types and return values" >&2
echo " • Add usage examples for complex functions" >&2
echo " • Keep README.md up-to-date with recent changes" >&2
echo " • Use consistent documentation format (JSDoc/TSDoc/etc)" >&2
if [ -s "$REPORT_FILE" ]; then
echo "" >&2
echo "📄 Documentation report: $REPORT_FILE" >&2
fi
elif [[ "$FILE_PATH" == *README* ]] || [[ "$FILE_PATH" == *CHANGELOG* ]]; then
echo "📝 Documentation file updated: $(basename "$FILE_PATH")" >&2
echo "✅ Keep documentation current with code changes" >&2
fi
exit 0
Hook reports low coverage but functions have inline comments
Hook detects structured docstrings (JSDoc/TSDoc) not inline comments. Convert // comments to /** */ JSDoc format. Use @param and @returns tags for proper documentation detection.
Python interrogate not found but installed in virtualenv
Activate virtualenv before hook runs: source venv/bin/activate in shell config. Use absolute path to interrogate binary. Add virtualenv bin directory to PATH in hook script.
False positives on private/internal functions flagged as undocumented
Hook checks all functions regardless of visibility. Use naming conventions (_private in Python). Configure threshold lower for internal files. Add @internal JSDoc tag to suppress warnings.
Coverage threshold environment variable not applied
Export DOC_COVERAGE_THRESHOLD before hook execution. Check bash environment in hook context. Set in .clauderc or shell profile. Verify with echo $DOC_COVERAGE_THRESHOLD in hook script.
API endpoint detection triggers on test files with mock routes
Hook matches route patterns without context awareness. Exclude test directories from matchers: ! [[ $FILE_PATH == *test* ]]. Add separate threshold for test documentation.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters