Blocks commits with unclosed or malformed markdown code fences.
bun add -d markdownlint-cli2.markdownlint-cli2.jsonc in project root:
.husky/lib/check-markdown-fences.sh:
#!/bin/sh
check_markdown_fences() {
STAGED_MD=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.md$' || true)
[ -z "$STAGED_MD" ] && return 0
violations=0
# Parity check: unclosed fences (markdownlint won't catch these)
for file in $STAGED_MD; do
[ -f "$file" ] || continue
fence_count=$(grep -cE '^(`{3,}|~{3,})' "$file" || true)
fence_count=${fence_count:-0}
if [ $(( fence_count % 2 )) -ne 0 ]; then
echo "❌ ERROR: Unclosed code fence in: $file ($fence_count fence markers)"
grep -nE '^(`{3,}|~{3,})' "$file" | sed 's/^/ /'
echo ""
violations=$(( violations + 1 ))
fi
done
# Style check via markdownlint
if ! ./node_modules/.bin/markdownlint-cli2 $STAGED_MD 2>&1; then
violations=$(( violations + 1 ))
fi
[ $violations -gt 0 ] && echo "❌ Commit blocked: markdown fence errors." && return 1
return 0
}Source and call in .husky/pre-commit:
. "$HOOK_DIR/lib/check-markdown-fences.sh"
check_markdown_fences- MD040 is not autofixable — the tool can't guess language identifiers.
--fixflag fixes MD031 (blank lines) automatically:
markdownlint-cli2 --fix "**/*.md"- Parity check is line-based: counts
```and~~~openers. Will miscount fences inside HTML blocks (rare in practice). - For existing repos with many MD040 violations, bulk-fix with
sed:
find docs/ -name "*.md" -exec sed -i '' 's/^```$/```text/' {} +
{ "config": { "default": false, "MD031": true, // blank lines around fences "MD040": true, // language required on fences "MD046": true, // consistent fence style "MD048": true // backtick vs tilde consistency }, "ignores": [ "node_modules/**", "**/node_modules/**", "dist/**", "**/dist/**", "build/**", "playwright-report/**", "test-results/**", "coverage/**", ".git/**", ".idea/**" // add project-specific: generated-docs/**, research/**, etc. ] }