Validates documentation artifacts using industry-standard linters, for use with AI Coding Agents.
53
A self-contained Docker image that validates documentation artifacts (Markdown, Mermaid diagrams, JSON, XML, YAML, TOML) using industry-standard linters. Designed as a validation gate for AI Coding Agents, particularly the Documentation Writer mode, to ensure documentation quality before handoff.
doc-lint provides a unified linting interface for six documentation formats commonly produced by AI agents:
.md, .markdown) — validated with markdownlint-cli2.mmd, .mermaid) — validated with @mermaid-js/mermaid-cli (mmdc).json) — syntax validation with Node.js, optional schema validation with ajv.xml) — well-formedness validation with xmllint.yaml, .yml) — syntax validation with yq.toml) — syntax validation with Python tomliThe image auto-detects file types by extension, runs the appropriate linters, and aggregates exit codes. A non-zero exit indicates at least one lint failure.
This project is designed for AI Coding Agents operating in automated documentation workflows, specifically:
The tool is not intended for interactive human use, though it can be used that way.
doc-lint/
├── Dockerfile # Multi-stage build for doc-lint image
├── package.json # Node.js dependencies (linters)
├── SKILL.md # Skill definition for Documentation Writer mode
├── README.md # This file
├── bin/
│ ├── lint.sh # Main entrypoint script (copied as /opt/lint/bin/lint)
│ ├── cache.sh # Content-addressable cache helpers
│ ├── plugin-loader.sh # Plugin discovery and execution
│ ├── json-schema-check.js # JSON Schema validation helper (ajv)
│ └── formatters/ # Output formatters
│ ├── text.sh # Human-readable text output (default)
│ ├── json.sh # JSON structured output
│ ├── junit.sh # JUnit XML for CI test reporting
│ └── sarif.sh # SARIF v2.1.0 for GitHub Code Scanning
├── configs/
│ └── .markdownlint.yaml # Default markdownlint configuration
├── plugins/
│ ├── README.md # Plugin development guide
│ └── example-yaml-lint/ # Example YAML linter plugin
│ ├── plugin.json # Plugin manifest
│ ├── lint.sh # Plugin executable
│ └── README.md # Plugin documentation
└── test/
├── run-tests.sh # Test harness
└── fixtures/ # Test fixtures (valid/invalid samples)
DockerfileBuilds a node:20-bookworm-slim-based image with:
chromium and fonts for Mermaid diagram renderinglibxml2-utils for XML validationlinter, UID 1000)/opt/lint/bin/lintbin/lint.shBash script that:
--schema for JSON validation)Exit codes:
0 — all lints passed1 — at least one lint failed2 — usage or environment error (missing target, bad schema path)bin/json-schema-check.jsNode.js script that validates JSON data against a JSON Schema using
ajv. Invoked by lint.sh when --schema is provided.
configs/.markdownlint.yamlDefault configuration for markdownlint-cli2, tuned for AI-generated
documentation:
A repository-provided .markdownlint.* file in the working directory takes precedence over this bundled default.
The pre-built image is available on Docker Hub:
docker pull dheaps/doc-lint:latest
Alternatively, clone or update the repository from GitHub and build the image locally:
# 1. Check whether the image already exists
if ! docker image inspect doc-lint:latest >/dev/null 2>&1; then
# 2. Clone the repository (or pull if it already exists)
if [ ! -d "doc-lint" ]; then
git clone https://github.com/king-dopey/doc-lint.git
else
git -C doc-lint pull --ff-only
fi
# 3. Build the image from the cloned repository
docker build -t doc-lint:latest ./doc-lint
fi
The build context must include:
Dockerfilepackage.jsonconfigs/.markdownlint.yamlbin/lint.shbin/cache.shbin/plugin-loader.shbin/json-schema-check.jsbin/formatters/ (all formatter scripts)plugins/ (plugin system and example plugins)docker image inspect doc-lint:latest >/dev/null 2>&1 && echo "Image present" || echo "Image missing"
Mount the working directory and pass file paths or directories to lint:
# Lint specific files
docker run --rm \
-v "$PWD:/work" -w /work \
doc-lint:latest lint docs/CHANGELOG.md docs/api.md
# Lint an entire directory (recursively)
docker run --rm \
-v "$PWD:/work" -w /work \
doc-lint:latest lint docs/
# Validate JSON against a JSON Schema
docker run --rm \
-v "$PWD:/work" -w /work \
doc-lint:latest lint --schema schemas/review-report.schema.json docs/review-report.json
doc-lint supports multiple output formats for different use cases:
# Human-readable text output (default)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --format text docs/
# JSON structured output (for programmatic consumption)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --format json docs/
# JUnit XML (for CI test reporting)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --format junit docs/ > test-results.xml
# SARIF v2.1.0 (for GitHub Code Scanning)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --format sarif docs/ > results.sarif
Enable parallel linting across file formats for improved performance:
# Run linters in parallel (faster for large repositories)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --parallel docs/
# Explicitly disable parallel processing (default behavior)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --no-parallel docs/
Enable content-addressable caching to skip unchanged files:
# Enable caching (mount cache volume for persistence)
docker run --rm \
-v "$PWD:/work" -w /work \
-v doc-lint-cache:/home/linter/.cache/doc-lint \
doc-lint:latest lint --cache docs/
# Disable caching (default)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --no-cache docs/
# Clean old cache entries (older than 30 days)
docker run --rm \
-v doc-lint-cache:/home/linter/.cache/doc-lint \
doc-lint:latest --cache-clean 30
Exclude files or directories from linting:
# Exclude specific patterns
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --exclude '*.draft.md' --exclude 'vendor/*' docs/
# Exclude multiple patterns
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint \
--exclude 'node_modules/*' \
--exclude '*.tmp' \
--exclude 'archive/*' \
docs/
Automatically fix common linting violations:
# Auto-fix violations (creates .bak backup files by default)
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --fix docs/
# Auto-fix without creating backups
docker run --rm -v "$PWD:/work" -w /work \
doc-lint:latest lint --fix --no-backup docs/
Supported auto-fixes:
markdownlint-cli2 --fix)prettier)Note: Auto-fix mode creates .bak backup files by default. Use --no-backup to skip backup creation.
Extend doc-lint with custom linters using the plugin system:
# Mount custom plugins directory
docker run --rm \
-v "$PWD:/work" -w /work \
-v "$PWD/plugins:/opt/lint/plugins" \
doc-lint:latest lint docs/
# Use custom plugins directory location
docker run --rm \
-v "$PWD:/work" -w /work \
-v "$PWD/my-plugins:/custom/plugins" \
doc-lint:latest lint --plugins-dir /custom/plugins docs/
Creating a plugin:
plugin.json manifestfile:line:col:rule:severity:messageplugins/example-yaml-lint/ for a complete exampleSee plugins/README.md for detailed plugin development guide.
.md, .mmd, .json,
.xml, .yaml, .yml, .toml), run doc-lint on exactly those files.0), set the handoff evidence_ref to the lint command + "ALL PASS".1), do not hand off. Emit a
handoff payload with status=FAIL, blocker_tag=tool-failure, and
route back via switch_mode → code for rework.The entrypoint prints one ==> [label] command line per check with an OK or FAIL result, followed by a summary:
==> [markdown] npx --no-install markdownlint-cli2 docs/README.md
OK
==> [json-syntax:docs/config.json] node -e "JSON.parse(...)" docs/config.json
OK
doc-lint: ALL PASS
On failure, read the linter's stderr for specific rule codes and line numbers:
MDxxx rule code and file:lineinstancePath + messagexmllint reports byte/line of the first well-formedness violationyq reports line number and error messagetomli reports line and column for syntax errorsdoc-lint uses an intermediate format internally to collect linting results from all linters. This format is also used by plugins to report their findings.
Format:
file:line:col:rule:severity:message
Fields:
file: Path to the file being linted (relative to working directory)line: Line number where the issue occurs (1-based)col: Column number where the issue occurs (1-based)rule: Rule identifier (e.g., MD013, yaml-syntax, json-syntax)severity: Severity level (error, warning, or info)message: Human-readable description of the issueExample:
docs/config.yaml:5:3:yaml-syntax:error:mapping values are not allowed here
docs/api.md:10:1:MD013:error:Line length exceeded
Usage:
--format json or --format sarif, results are converted from this intermediate formatDOC_LINT_WORKDIR — working directory inside the container (default /work)DOC_LINT_MERMAID_OUT — directory where mmdc writes temporary SVGs (default /tmp/doc-lint-mermaid)DOC_LINT_PLUGINS_DIR — custom plugin directory (default /opt/lint/plugins)DOC_LINT_PLUGIN_TIMEOUT — plugin execution timeout in seconds (default 30)DOC_LINT_FIX — enable auto-fix mode (default false)DOC_LINT_NO_BACKUP — disable backup creation in auto-fix mode (default false)Mermaid validation via rendering: mmdc does not have a dedicated
--parse flag; validation requires rendering to SVG, which is slower
than pure syntax checking. This is the documented behavior of the
official CLI.
Image size: The inclusion of Chromium for Mermaid rendering makes
the image larger (~1.5GB) than a minimal linter image. A lighter
alternative would be a standalone Rust-based Mermaid parser, but
mmdc is the official tool and guarantees compatibility.
JSON Schema validation is opt-in: Plain JSON files receive
syntax-only checks by default. Schema validation requires passing
--schema <path> explicitly.
Auto-fix limitations: Auto-fix mode only supports Markdown and JSON. YAML, TOML, XML, and Mermaid files cannot be auto-fixed. Some Markdown violations (e.g., MD025 multiple top-level headings) require manual intervention.
doc-lint is designed with security in mind. The linters do not require network access or persistent storage, making the container suitable for restricted environments.
For maximum security, run the container with these flags:
docker run --rm \
--network=none \
--read-only \
--tmpfs /tmp \
--security-opt=no-new-privileges \
--cap-drop=ALL \
-v "$PWD:/work" -w /work \
doc-lint:latest lint docs/
Flag explanations:
--network=none: Disables all network access. Linters are offline tools
and do not need network connectivity.--read-only: Makes the container filesystem read-only, preventing any
writes except to explicitly mounted volumes or tmpfs.--tmpfs /tmp: Provides a temporary writable filesystem for /tmp, which
is required for Mermaid diagram rendering (SVG output) and other temporary
operations.--security-opt=no-new-privileges: Prevents processes from gaining
additional privileges via setuid/setgid binaries.--cap-drop=ALL: Drops all Linux capabilities. The linters do not require
any special capabilities.--read-only with Mermaid: Mermaid rendering requires writing SVG files to /tmp.
The --tmpfs /tmp flag provides a writable temporary directory while keeping
the rest of the filesystem read-only.--read-only: If using --cache, mount a volume for the cache directory: -v doc-lint-cache:/home/linter/.cache/doc-lint.You can verify the container's security posture using these tools:
# Inspect container configuration
docker inspect doc-lint:latest
# Analyze image layers and security
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest doc-lint:latest
# Check for vulnerabilities (requires Trivy)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image doc-lint:latest
Third-party binaries downloaded during image build are verified against the
release's SHA256SUMS artifact. For example, the yq binary download
verifies its SHA256 checksum before installation; a mismatch causes the
Docker build to fail. This prevents supply-chain compromise of pinned
dependencies.
Structured formatters (JSON, SARIF) use language-native serialization
(JSON.stringify) which inherently escapes special characters. The JUnit
XML formatter now explicitly escapes &, <, >, ", and ' in all
interpolated values to prevent XML injection from crafted linter or plugin
output.
The container runs as a non-root user (linter, UID 1000) by default. This
limits the impact of any potential vulnerabilities in the linters or their
dependencies.
This project is licensed under the MIT License. See the LICENSE file for details.
This project is provided as-is for use in AI Coding Agent workflows. No warranty is expressed or implied.
Content type
Image
Digest
sha256:0911abf41…
Size
560.5 MB
Last updated
2 days ago
docker pull dheaps/doc-lint