Forge-core MarkdownLint

Format and lint a markdown document — backtick code references, fix bare URLs, check list formatting and heading hierarchy. USE WHEN a document needs formatting cleanup, code references are not backticked, markdown quality needs improvement, or lint markdown.

install
source · Clone the upstream repo
git clone https://github.com/N4M3Z/forge-core
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/N4M3Z/forge-core "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/MarkdownLint" ~/.claude/skills/n4m3z-forge-core-markdownlint && rm -rf "$T"
manifest: skills/MarkdownLint/SKILL.md
source content

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-read
    via 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:

ZoneDetectionAction
YAML frontmatterLines between opening
---
and closing
---
at file start
Skip entirely
Fenced code blocksLines between
```
or
~~~
fences
Skip entirely
Inline codeText between single backticksSkip entirely
WikilinksText matching
[[...]]
Preserve as-is — never backtick
Obsidian embedsLines starting with
![[
or
![
Preserve
HTML blocks
<tag>...</tag>
blocks
Skip entirely
Obsidian comments
%% ... %%
blocks
Skip entirely

Process only unprotected text spans.

Step 4: Apply formatting rules

3a: Backtick filenames

Any word ending in a code-associated extension:

.md
,
.yaml
,
.yml
,
.json
,
.rs
,
.sh
,
.ts
,
.js
,
.toml
,
.base
,
.css
,
.html
,
.py
,
.go
,
.lua
,
.sql
,
.env
,
.tlp
,
.gitignore
,
.claudeignore

Examples: CLAUDE.md becomes

CLAUDE.md
, config.yaml becomes
config.yaml
.

Skip if already backticked, inside a wikilink, or part of a URL path.

3b: Backtick CLI commands and tool names

Recognized patterns:

  • Known tools:
    safe-read
    ,
    safe-write
    ,
    blind-metadata
    ,
    obsidian-base
    ,
    build-templates
    ,
    surface
    ,
    insight
    ,
    reflect
    ,
    ekctl
    ,
    cargo
    ,
    make
    ,
    git
    ,
    npm
    ,
    shellcheck
    ,
    jq
  • Command invocations with flags: words followed by
    --flag
    or
    -f
  • Make targets:
    make install
    ,
    make test
    ,
    make clean
  • Compound commands:
    cargo build
    ,
    cargo test
    ,
    git commit

Backtick the full command phrase, not individual words.

3c: Backtick technical identifiers

PatternExampleRule
Effort/log tags#log/effort/shortBacktick the full tag
TLP tags as references#tlp/redBacktick when discussed as a value (not when used as actual inline tag)
Environment variablesFORGE_MODULE_ROOTBacktick ALL_CAPS_UNDERSCORE identifiers
Config keys in proseuser.root, shared.journal.dailyBacktick dot-separated paths
YAML keys referenced in prosesource_module:, description:Backtick when referenced as field names

Exceptions: Tags used as actual functional Obsidian tags — leave as-is. Env vars inside code blocks — skip.

3d: Convert bare URLs

Bare URLs (

<https://...>
or plain
https://...
in text):

  1. GitHub repo URL: use repo name —
    [forge-tlp](https://github.com/user/forge-tlp)
  2. Well-known service: use service name —
    [Obsidian](https://obsidian.md/)
  3. Other: infer title from URL path or use domain name
  4. 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 write
    via 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