Skip to content

Repository files navigation

zero-md-formatter

skills.sh GitHub Release npm version npm downloads CI

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.


Quick start

Install from npm

npm install -g zero-md-formatter
mdfmt --fix README.md

Requires Node.js >=24. Zero runtime npm dependencies — no config file, no plugin system.

With pnpm:

pnpm add -g zero-md-formatter
mdfmt --fix README.md

Update 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.

Node.js support policy

The package supports Node.js 24 and newer. CI validates the Node.js 24.x support floor and the current Node.js 26.x line; Node.js versions below 24 are not supported. The .node-version file remains the local development baseline.

Use via npx (no install)

npx zero-md-formatter --fix README.md

Use programmatically

import { formatContent } from 'zero-md-formatter';

const result = formatContent(rawMarkdown);
console.log(result);

Run from source

git clone https://github.com/CodeSigils/zero-md-formatter.git
cd zero-md-formatter
node src/index.js --fix --guard README.md

What it does

Formatter-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 --guard is used

What it doesn't do

  • 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.

CLI reference

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

File exclusion

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 (*.md does not match docs/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 .mdfmtignore to preserve that behavior:

.github/
.agents/
.opencode/

Examples

# 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 --doctor

Table and pipe safety

GFM 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.


Agent skill usage

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.

Install as a skill

With the standard skills CLI:

npx skills add CodeSigils/zero-md-formatter --skill markdown-formatter
Hermes Agent

Recommended for development — clone the repo and add to external_dirs:

skills:
  external_dirs:
    - /path/to/zero-md-formatter/skills

Every 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 --yes

Then use the formatter via npm (recommended — gives mdfmt binary):

npm install -g zero-md-formatter
mdfmt --fix --guard README.md

Or run from source (no npm install):

node src/index.js --fix --guard README.md

For 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: patch

This 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-formatter

For 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.md
Claude 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.md

Or 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.md

For 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-formatter

OpenCode and Gemini CLI also discover .agents/skills/markdown-formatter/. Claude Code also supports $HOME/.claude/skills/markdown-formatter/ for user-wide installs.

Portability

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)

Safety policy

Reference spec: GitHub Flavored Markdown Spec.

  • check-tables.js enforces 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.js detects 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 --guard fail fence validation without modifying the file. Unguarded write modes continue formatting around the open fence.
  • Write-mode --guard runs structural snapshots before and after formatting. If post-format structure doesn't match the pre-format snapshot, the original content is restored.

Supported file types

  • .md
  • .markdown
  • .mdx

Prerequisites

  • Node.js >=24
  • jq (Hermes shell hook only)

Run mdfmt --doctor to verify runtime readiness.


Install payload

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.


Project files


Maintaining

Everyday changes

After changing runtime code, guards, tests, or documentation:

npm test
npm run format:check
bash scripts/staged-install-verify.sh

npm 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.js

Dependency and action freshness

Dependabot 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.

CI and pull requests

Every pull request must pass the deterministic lint gate and both runtime matrix jobs (test (24.x) and test (26.x)). The lint gate runs formatting, payload synchronization, dependency audit, offline link checks, and builds the single npm tarball used by publishing. It also installs that tarball into a clean temporary prefix and exercises the packaged mdfmt binary; the runtime jobs focus on the test suite across supported Node versions. Publishing is allowed only after all three gates succeed.

Live HTTP checks are intentionally not required for pull requests. The external-contracts job runs weekly and on demand from the Actions tab, so an upstream outage does not make an otherwise deterministic change unmergeable.

Evidence URLs

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. The scheduled/manual external-contracts job performs live HTTP verification.

After checking the links live, refresh the timestamps with:

node scripts/verify-urls.mjs --update

Do not use --update without a successful live verification.

For local relative Markdown links, run:

npm run check:links

This offline check validates links such as [regression guide](docs/codex-regression.md); external URLs remain covered by verify:urls.

Release process

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 publish job publishes the exact npm tarball built and 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.sh

The 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.

Release troubleshooting

  • 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 rerun release.sh from 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.

Behavior harness

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.


License

MIT

About

Zero-dependency GFM and MDX formatter with table, pipe, and fence guards for AI-agent-authored Markdown

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages