MarkdownLint
Format and lint a markdown document for consistent code formatting, proper list structure, heading hierarchy, and link hygiene. Reads a file, applies all rules, writes the corrected version after confirmation.
Instructions
Step 1: Read the target file
If an argument was provided, use it as the file path. Otherwise, ask which file to lint.
Check TLP before reading:
- GREEN/CLEAR: Read directly
- AMBER: Use
safe-readvia Bash - RED: Refuse
Step 2: Schema validation
If mdschema is installed, look for a .mdschema in the file's parent directory (for skills: skills/.mdschema, for agents: agents/.mdschema). If found, run:
mdschema check "<file>" --schema "<schema>"
Report any violations (missing frontmatter fields, heading level skips, depth exceeded). These are structural issues that formatting rules cannot auto-fix — present them to the user as findings.
If mdschema is not installed or no schema exists, skip this step silently.
Step 3: Identify protected zones
Before applying any rules, identify zones that MUST NOT be modified:
| Zone | Detection | Action |
|---|---|---|
| YAML frontmatter | Lines between opening --- and closing --- at file start |
Skip entirely |
| Fenced code blocks | Lines between ``` or ~~~ fences |
Skip entirely |
| Inline code | Text between single backticks | Skip entirely |
| Wikilinks | Text matching [[...]] |
Preserve as-is — never backtick |
| Obsidian embeds | Lines starting with  - Well-known service: use service name —
[Obsidian](https://obsidian.md/) - Other: infer title from URL path or use domain name
- If WebFetch available and URL is public, fetch the page title
Never convert URLs already inside markdown links [text](url) or code blocks.
3e: Fix list formatting
- Use
-for unordered lists (Obsidian convention) - Proper indentation for child items
- Blank line before and after list blocks (unless inside a callout)
3f: Check heading hierarchy
- No skipped levels (e.g.,
##followed by####) - Single
#H1 at document start (after frontmatter) - Flag but do not auto-fix if structure is ambiguous
Step 5: Special syntax recognition
Do NOT lint these as errors:
#tlp/red,#tlp/amber,#tlp/green,#tlp/clear— valid TLP structural markers%% @todo ... %%,%% @fixme ... %%— valid Obsidian annotation markers![[embed]]— valid Obsidian embed syntax> [!callout]— valid Obsidian callout syntax- Dataview blocks (
dataview,dataviewjs)
Step 6: Confirm changes
Present a summary:
- Count of backticked items by category (filenames, commands, tags, env vars)
- Count of URLs converted
- List/heading fixes
- 2-3 representative before/after examples
Ask the user to confirm before writing.
Step 7: Write the corrected file
- AMBER: use
safe-write writevia Bash - GREEN/CLEAR: use Write tool directly
- Preserve all protected zones exactly
Constraints
- Never modify YAML frontmatter content
- Never modify content inside fenced code blocks or inline code
- Never backtick text inside wikilinks
- Never convert wikilinks to markdown links or vice versa
- Never change semantic meaning — only formatting
- Never add backticks to regular English words (e.g., "base" as a noun, "draft" as a verb)
- Preserve all Obsidian-specific syntax
- After linting, suggest the WikiLink skill if the document could benefit from knowledge graph links