Zero-dependency GFM and MDX formatter with structural guardrails — trailing whitespace removal, table alignment, fence normalization, pipe-safety checks, column-count enforcement, and drift detection.
Designed for AI-agent workflows but works anywhere Node.js >=24 runs.
npm install -g zero-md-formatter
mdfmt --fix README.md
The CLI and formatter module have zero npm runtime dependencies. Installable on any system with Node.js >=24.
npm install -g zero-md-formatter
mdfmt --fix README.mdRequires Node.js >=24. Zero runtime npm dependencies — no config file, no plugin system.
With pnpm:
pnpm add -g zero-md-formatter
mdfmt --fix README.mdUpdate an existing global pnpm installation with pnpm -g update zero-md-formatter. If the global dependency is pinned to an exact version,
use pnpm add -g zero-md-formatter@latest to advance it explicitly.
The package supports Node.js 24 and newer. CI validates Node.js 24.x and the
current Node.js 25.x line; Node.js versions below 24 are not supported. The
.node-version file remains the local development baseline.
npx zero-md-formatter --fix README.mdimport { formatContent } from 'zero-md-formatter';
const result = formatContent(rawMarkdown);
console.log(result);git clone https://github.com/CodeSigils/zero-md-formatter.git
cd zero-md-formatter
node src/index.js --fix --guard README.mdFormatter-owned behavior:
- Remove trailing whitespace
- Ensure a final newline
- Normalize leading-tab indentation outside fenced code blocks
- Align GFM table columns when the table has no empty-cell ambiguity
- Normalize tilde fences to backtick fences, escalating the backtick count when nested content requires it; existing backtick fences are never modified
Guard-owned behavior:
- Fence closure and malformed fence info strings
- Table column counts (header vs delimiter vs data row alignment)
- Unescaped inline-code pipes in table rows
- Adjacent-pipe table hazards (
||→| |) - Pre/post structural drift detection and rollback when
--guardis used
- No formatting config file — no
.prettierrc,.markdownlintrc, or similar. No plugin system. Zero runtime dependencies means no extension points. - No dialect extensions — no Obsidian wiki-links, Mermaid, Pandoc, or frontmatter semantics.
- No JSX/MDX validation — formats Markdown containers only; JSX inside is passed through unchecked.
mdfmt [options] <path...>| Flag | Description |
|---|---|
--check |
Read-only pipe-safety and format check (exit 0 if clean) |
--fix |
Format files in-place after pipe-safety preflight (default) |
--all |
Process directories recursively |
--guard |
Pre/post structural check; rollback on drift; clean snapshots |
--verify |
Run formatting, idempotence, and structural checks without writing |
--fences |
Validate fenced code block info strings |
--validate |
Run all structural validations |
--doctor |
Check runtime prerequisites without modifying files |
--dry-run, -n |
Run pipe-safety preflight, preview changes without writing |
--audit-tables |
Print table row cell counts and pipe hazards without writing |
--no-repair |
Report repairable table issues instead of modifying them |
--version |
Print version number and exit |
--help, -h |
Display help message |
Create .mdfmtignore in the project root to exclude files from --all and explicit path processing. One pattern per line; # for comments. Patterns ending with / match directories; glob characters are honored there too (build*/, **/tmp/).
*matches any characters within a single path segment**matches across any number of path segments (docs/**/*.md,**/generated/)- Everything else matches literally or as a path prefix
- Patterns without a
/are matched against the project root only (*.mddoes not matchdocs/a.md) ?and[...]have no special meaning and match literally;!negation is not supported
# Skip vendored docs and generated output
vendor/
docs/generated/
*.generated.md
docs/**/*.draft.md
**/internal/
Dot-directories are scanned by default as of v1.5.0. Previously hidden folders like
.github/were always skipped; add them to.mdfmtignoreto preserve that behavior:.github/ .agents/ .opencode/
# Check formatting (read-only, CI-safe)
mdfmt --check README.md
# Format with rollback-safe structural guards
mdfmt --fix --guard docs/
# Validate structure across a directory
mdfmt --validate --all docs/
# Diagnose installed readiness
mdfmt --doctorGFM tables are notoriously fragile in agent-generated Markdown. This formatter includes guard scripts that catch the most common failure modes before formatting:
- Adjacent pipes (
||) create empty cells per GFM. Write modes automatically insert a space (| |), preserving empty-cell semantics. Read-only modes block with a clear error. - Inline-code pipes (
|cmd | opt| title |) look like extra columns to naive formatters. Guard scripts detect them and block formatting before corruption. - Column drift — rows with mismatched column counts are detected and, in write mode, repaired by padding short rows or rolling back on structural drift.
- Empty-cell tables that remain ambiguous are preserved by skipping the full formatter pass. The delimiter row is still normalized to GFM-canonical width.
- Unclosed-fence preflight — all modes detect unclosed fences before running table/pipe checks and skip validation that cannot be trusted while a fence is open. Read-only and guarded modes fail without modifying the file; unguarded write modes warn and continue formatting around the open fence.
Table-shaped content inside fenced code blocks is always left untouched.
The formatter ships as a standard agentskills-compatible skill via
SKILL.md. It works with any agent that supports
agentskills.io-formatted skills.
The optional Codex behavioral regression checks that an
agent both performs guarded formatting and respects a blocking pipe hazard.
Normal CI runs the deterministic behavior self-tests as part of npm test.
With the standard skills CLI:
npx skills add CodeSigils/zero-md-formatter --skill markdown-formatterHermes Agent
Recommended for development — clone the repo and add to external_dirs:
skills:
external_dirs:
- /path/to/zero-md-formatter/skillsEvery commit is immediately reflected without reinstalling.
For end users — install from hub:
# Add repo as skill tap (one-time), then install
hermes skills tap add CodeSigils/zero-md-formatter
hermes skills install CodeSigils/zero-md-formatter/markdown-formatter --yesThen use the formatter via npm (recommended — gives mdfmt binary):
npm install -g zero-md-formatter
mdfmt --fix --guard README.mdOr run from source (no npm install):
node src/index.js --fix --guard README.mdFor auto-wiring on every write_file or patch call — the hook script ships with the
skill. You just need to register it:
# The script is already at:
# ~/.hermes/skills/markdown-formatter/scripts/check-markdown.sh
# No download needed.Then add the hook to config.yaml:
hooks:
post_tool_call:
- command: ~/.hermes/skills/markdown-formatter/scripts/check-markdown.sh
matcher: write_file
- command: ~/.hermes/skills/markdown-formatter/scripts/check-markdown.sh
matcher: patchThis runs --fix --guard on every written Markdown file — formatting, repairing
adjacent pipes, normalizing fences, aligning tables, and rolling back on
structural drift before they reach git.
Codex CLI
For a repo-specific Codex skill, copy the tap payload into .agents/skills:
mkdir -p .agents/skills
cp -R skills/markdown-formatter .agents/skills/markdown-formatterFor a user-wide Codex skill, copy it to $HOME/.agents/skills instead.
Codex also works directly with the CLI:
npm install -g zero-md-formatter
mdfmt --fix --guard README.mdClaude Code / OpenCode / Gemini CLI
All three can run the formatter as a normal shell CLI:
npm install -g zero-md-formatter
mdfmt --fix --guard README.mdOr clone the source and run the bundled CLI directly:
git clone https://github.com/CodeSigils/zero-md-formatter.git
node zero-md-formatter/src/index.js --fix --guard README.mdFor native Agent Skills support, copy the tap payload to the runtime's documented skill directory:
# Claude Code
mkdir -p .claude/skills
cp -R skills/markdown-formatter .claude/skills/markdown-formatter
# OpenCode
mkdir -p .opencode/skills
cp -R skills/markdown-formatter .opencode/skills/markdown-formatter
# Gemini CLI
mkdir -p .gemini/skills
cp -R skills/markdown-formatter .gemini/skills/markdown-formatterOpenCode and Gemini CLI also discover .agents/skills/markdown-formatter/.
Claude Code also supports $HOME/.claude/skills/markdown-formatter/ for
user-wide installs.
| Component | Portable? |
|---|---|
CLI (src/index.js) |
Pure Node.js, no agent runtime required |
| SKILL.md | agentskills.io base frontmatter |
| Guard modules | Node.js, no agent tools referenced |
| Post-write hook config | Hermes-specific (platform feature) |
Reference spec: GitHub Flavored Markdown Spec.
check-tables.jsenforces formatter-safe table column counts and pipe consistency, including unescaped pipes inside inline code spans. Stricter than GFM body-row parsing because autonomous formatting should not guess table intent.check-pipes.jsdetects adjacent pipes in table rows, which create valid empty cells per GFM. Write modes repair them by inserting a space between the pipes. Read-only modes block with a clear error.- All CLI modes run pipe-safety preflight checks before table operations. When
an unclosed fence is detected, the CLI warns that table and pipe checks are
unreliable and skips them. Read-only modes and write mode with
--guardfail fence validation without modifying the file. Unguarded write modes continue formatting around the open fence. - Write-mode
--guardruns structural snapshots before and after formatting. If post-format structure doesn't match the pre-format snapshot, the original content is restored.
.md.markdown.mdx
- Node.js >=24
jq(Hermes shell hook only)
Run mdfmt --doctor to verify runtime readiness.
The shipped runtime payload contains:
zero-md-formatter/
SKILL.md
src/index.js
src/repairs.js
src/format-content.mjs
guard/check-structure.js
guard/check-fences.js
guard/check-tables.js
guard/check-pipes.js
guard/fence-utils.js
scripts/check-markdown.sh
The npm tarball also includes package metadata, README.md, and LICENSE.
Repository-only files (test/, .github/) are excluded via the files
field in package.json — scripts/ is not shipped with npm, except
scripts/check-markdown.sh which is included in the Hermes tap payload
(skill install) but not the npm package.
SKILL.md— packaged skill instructionssrc/index.js— CLI entrypointsrc/format-content.mjs— formatter modulescripts/check-consistency.js— repository drift checks
After changing runtime code, guards, tests, or documentation:
npm test
npm run format:check
bash scripts/staged-install-verify.shnpm test covers structural fixtures, unit tests, integration tests,
consistency checks, and deterministic behavior tests. The pre-commit hook runs
the same test suite plus the formatting check. Runtime files listed by
scripts/runtime-payload.js must be synchronized
with skills/markdown-formatter/; run this after runtime edits:
node scripts/sync-tap-payload.jsDependabot checks npm metadata and pinned GitHub Actions weekly. It groups compatible minor/patch npm updates and action updates into focused pull requests; CI remains the merge gate. Review major npm updates separately for runtime or formatting behavior changes.
docs/evidence-urls.json records the external
references used by the README and skill. Unit tests enforce that every
last_verified value is no more than 30 days old; CI also performs live HTTP
verification.
After checking the links live, refresh the timestamps with:
node scripts/verify-urls.mjs --updateDo not use --update without a successful live verification.
For local relative Markdown links, run:
npm run check:linksThis offline check validates links such as [regression guide](docs/codex-regression.md); external
URLs remain covered by verify:urls.
The npm and GitHub Release badges above show the current published version.
Every release tag and npm package is published from the same CI-verified
tarball. GitHub Releases are the canonical source for automatically generated
release notes; use the
latest release
or the full release history.
Publishing currently uses a protected npm token with provenance. The planned
OIDC migration requires configuring the npm trusted publisher for ci.yml
before removing that token.
Runtime changes must be merged before the isolated version-bump commit.
release.sh requires a clean tree, synchronized skill metadata, a stable
x.y.z version, an isolated version commit, a pushed main, and successful
CI. It creates the tag and GitHub Release; the tag workflow publishes the exact
npm tarball tested by CI.
npm version patch --no-git-tag-version # or minor/major
git add package.json package-lock.json SKILL.md skills/markdown-formatter
git commit -m "chore(release): bump version to X.Y.Z"
git push origin main
DRY_RUN=1 bash scripts/release.sh
bash scripts/release.shThe dry run validates all release preconditions without creating a tag,
pushing, or publishing. Do not run regular npm version here because it
creates a tag before release.sh can perform its checks.
-
If CI fails before publishing, fix the failing commit, push it, and rerun the release preflight.
-
If a tag was created but npm publication failed, confirm the version is not present with
npm view zero-md-formatter versions --json. Remove the unpublished GitHub Release and remote tag, then rerunrelease.shfrom a clean, tested commit. -
Verify the result from both clients:
npm view zero-md-formatter version pnpm -g update zero-md-formatter mdfmt --version
Never reuse a tag for a version that has already been published to npm.
npm run test:behavior runs deterministic fixture and grader self-tests. The
live Codex evaluation is optional and manual; its commands and artifact policy
are documented in docs/codex-regression.md.
MIT