This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mdformat-space-control is an mdformat plugin that provides unified control over Markdown spacing:
- EditorConfig support: Configure list indentation via
.editorconfigfiles - Tight list formatting: Automatically removes unnecessary blank lines between list items
- Frontmatter spacing: Normalizes spacing after YAML frontmatter (works with mdformat-frontmatter)
- Consecutive blank line normalization: Limits runs of 3+ empty lines to a maximum of 2
- Trailing whitespace removal: Strips trailing whitespace outside code blocks
- Smart dash conversion: Converts
--to en-dash and---to em-dash, with protection for HTML comments, tags, code blocks, and inline code spans - Escaped link repair: Fixes malformed multi-line links from web-clipped content
- Wikilink preservation: Handles Obsidian-style
[[links]],[[links|aliases]],[[page#heading]],[[page#^blockid]], and![[embeds]] - Soft break joining: Joins soft breaks (plain newlines) into single lines in paragraphs, list items, and blockquotes
This plugin merges functionality from mdformat-editorconfig and mdformat-tight-lists into a single plugin, solving the issue where mdformat only applies one set of list renderers when multiple plugins are installed.
uv sync --extra test # Install dependencies including test deps
uv run python -m pytest # Run all tests
uv run python -m pytest -v # Run tests verbosely
uv run python -m pytest --cov=mdformat_space_control # Run with coverage
uv run python -m pytest tests/test_editorconfig.py # Run specific test filemdformat_space_control/
├── __init__.py # Public API exports, version
├── config.py # EditorConfig lookup, file context tracking
└── plugin.py # Renderers, postprocessors, parser extensions
Key components:
config.py: Usescontextvarsfor thread-safe file path tracking. Falls back toPath.cwd() / "_.md"for CLI usage when no explicit file context is set.plugin.py: Provides renderers, postprocessors, and parser extensions:_wikilink_rule: Inline parser for Obsidian-style wikilinks_render_wikilink: Preserves wikilinks unchanged_render_softbreak: Joins soft breaks (plain newlines) into single lines with spaces_render_list_item: Per-item tight/loose formatting based on paragraph count_render_bullet_list: Configurable indent + content-based tight/loose_render_ordered_list: Configurable indent + content-based tight/loose_postprocess_root: Combined postprocessor applying frontmatter spacing, smart dash conversion, escaped link repair, consecutive blank line normalization, and trailing whitespace removal
mdformat plugins expose:
RENDERERS: Dict mapping node types to render functionsPOSTPROCESSORS: Dict mapping node types to postprocess functionsupdate_mdit(mdit): Hook to modify the markdown-it parser (used for wikilink parsing)
Entry point in pyproject.toml:
[project.entry-points."mdformat.parser_extension"]
space_control = "mdformat_space_control"tests/fixtures.md: Markdown-it fixture format for tight-list teststests/test_fixtures.py: Parametrized fixture teststests/test_editorconfig.py: EditorConfig-specific tests using temp directoriestests/test_frontmatter.py: Frontmatter spacing tests (requires mdformat-frontmatter)tests/test_dash_conversion.py: Smart dash conversion with HTML comment/tag protectiontests/test_spacing_features.py: Trailing whitespace, hard breaks, escaped link repair, consecutive blank line teststests/test_integration.py: Full-stack integration tests combining multiple featurestests/test_plugin_interactions.py: Tests for compatibility with other mdformat plugins
- mdformat (>=0.7.0): The Markdown formatter being extended
- editorconfig (>=0.12.0): EditorConfig file parsing
Tested to work alongside:
mdformat-frontmatter- YAML frontmatter parsingmdformat-simple-breaks- Normalizes thematic breaks to---mdformat-gfm- GFM table parsing (prevents hard breaks in pipe table rows)
Note: Wikilink support is built-in; mdformat-wikilink is not needed.
- Update version in
__init__.py - Commit changes
- Tag with
git tag vX.Y.Z - Push tag to trigger PyPI publish:
git push origin vX.Y.Z
This project uses the global ~/.claude/settings.json for all permissions and settings.
The tlmgr command manages all tool repositories from the umbrella ~/tools directory:
tlmgr --json summary # Overall status of all 14 repos
tlmgr --json list # Detailed status with branches
tlmgr changes # Show uncommitted changes
tlmgr unpushed # Show unpushed commitsAlways use tlmgr (not relative paths like ./bin/tools-manager.sh).
Auto-allowed git operations:
- Read: status, diff, log, show, branch, grep, blame
- Write: add, commit, push, pull, checkout, switch, restore, stash
Require confirmation:
- Destructive: merge, rebase, reset, cherry-pick, revert
- Force operations: push --force
- Repository changes: clone, init, submodule
Python: pytest, pip, poetry, uv (install, run, sync)
Node: npm (test, run, install), node
Build: make, bash scripts in ./scripts/
Utilities: find, grep, rg, cat, ls, tree, jq, yq, head, tail, wc
Documents: pandoc, md2docx, mdformat
All permissions are centralized in ~/.claude/settings.json:
- Sandbox is disabled globally
- Full read/write access to ~/tools/** and ~/agents/**
- Standard security protections (no ~/.ssh, .env files, etc.)
- Consistent behavior across all projects
No project-specific .claude/ folders are needed.