π ζ₯ζ¬θͺ | δΈζ | νκ΅μ΄
A rule-based linter for structured Markdown documents. Catch broken references, duplicate IDs, missing sections, and structural issues β deterministically, in seconds, CI-friendly.
In the era of AI-driven development, methodologies like SDD (Spec Driven Development) are gaining traction β writing specifications in Markdown first, then letting AI generate implementation based on those documents. As projects adopt document-driven workflows, the number of interconnected Markdown files grows: requirements, design decisions, API specs, ADRs, RFCs, and more.
These documents form a dependency graph. IDs reference other IDs, files link to other files, and stability levels flow downstream. When this graph breaks β a deleted requirement, a mistyped ID, a missing section β the consequences are silent.
contextlint provides deterministic, static validation for structured Markdown. No AI, no cost, CI-friendly.
contextlint focuses on content semantics and cross-file integrity. For Markdown syntax, formatting, and style, use markdownlint alongside contextlint β they complement each other well.
Install:
npm install -D @contextlint/cliGenerate a config interactively:
npx contextlint initOr create contextlint.config.json manually:
{
"$schema": "https://raw.githubusercontent.com/nozomi-koborinai/contextlint/main/schema.json",
"include": ["docs/**/*.md"],
"rules": [
{ "rule": "tbl001", "options": { "requiredColumns": ["ID", "Status"] } },
{ "rule": "tbl002", "options": { "columns": ["ID", "Status"] } },
{ "rule": "ref001" }
]
}Run:
npx contextlintcontextlint auto-detects contextlint.config.json from the current
or any parent directory. The include field defines default file
patterns; CLI arguments override it. When neither is set, **/*.md
is used.
Output:
docs/requirements.md
line 3 warning Empty cell in column "Status" TBL-002
docs/design.md
line 12 error Link target "./api.md" does not exist REF-001
1 error, 1 warning in 2 files
Adding
$schemaenables autocomplete in VS Code, Cursor, JetBrains, and other editors.
| ID | Description | Config |
|---|---|---|
| TBL-001 | Required columns must exist in tables | requiredColumns, section?, files? |
| TBL-002 | Key columns must not have empty cells | columns?, files? |
| TBL-003 | Column values must be from an allowed set | column, values, files? |
| TBL-004 | Cell values must match a regex pattern | column, pattern, files? |
| TBL-005 | Cross-column conditional constraints | when, then, section?, files? |
| TBL-006 | IDs must be unique across all matched files | files, column, idPattern? |
| ID | Description | Config |
|---|---|---|
| SEC-001 | Required sections must exist | sections, files? |
| SEC-002 | Sections must appear in order | order, level?, section?, files? |
| STR-001 | Required files must exist in the project | files |
| ID | Description | Config |
|---|---|---|
| REF-001 | Relative links must point to existing files | exclude? |
| REF-002 | Defined IDs must be referenced; referenced IDs must exist | definitions, references, idColumn, idPattern |
| REF-003 | Stability must not exceed that of dependencies | stabilityColumn, stabilityOrder, definitions, references, idColumn?, idPattern? |
| REF-004 | Cross-zone links must be declared in overview | zonesDir, dependencySection? |
| REF-005 | Anchor fragments must point to existing headings | files? |
| REF-006 | Image references must point to existing files | exclude? |
| ID | Description | Config |
|---|---|---|
| CHK-001 | All checklist items must be checked | section?, files? |
| ID | Description | Config |
|---|---|---|
| CTX-001 | Sections must contain meaningful content, not placeholders | section?, placeholders?, files? |
| CTX-002 | Terms must match glossary definitions | glossary, termColumn, aliasColumn?, section?, files? |
| ID | Description | Config |
|---|---|---|
| GRP-001 | Every ID must be traceable through all stages of the document chain | chain, idPattern? |
| GRP-002 | Document reference graph must be acyclic (no circular references) | files?, exclude? |
| GRP-003 | Every document must have at least one incoming reference | files?, entryPoints? |
These rules are designed to be general-purpose. Some examples:
- SDD (Spec Driven Development) β Validate that specs reference existing requirements and that IDs are consistent across files
- ADRs (Architecture Decision Records) β Ensure all ADRs contain required sections (Status, Context, Decision) and that status transitions are valid
- RFCs (Request for Comments) β Check that RFC documents include required headings and that cross-references between proposals are not broken
- Any structured Markdown project β Catch broken links, duplicate IDs, and missing files in CI
contextlint init # Interactive config generationGenerates contextlint.config.json by selecting language, file patterns,
and rule categories interactively. Supports English, Japanese, Chinese,
and Korean.
contextlint [files...] # Lint structured Markdown documents
contextlint --format json # Machine-readable output
contextlint --watch # Re-run on file changescontextlint impact <file> # Analyze change impact + lint affected files
contextlint impact docs/req.md --format jsoncontextlint slice <query> # Extract related documents
contextlint slice docs/req.md --depth 3contextlint graph # Show document dependency graph
contextlint graph --format jsoncontextlint compile # Generate SKILL.md for Claude Code
contextlint compile --dry-run # Preview without writing
contextlint compile --outdir .claude/skills/my-skill| Option | Description |
|---|---|
[files...] |
File or glob patterns to lint (overrides include in config) |
--config <path> |
Path to contextlint.config.json |
--format <format> |
Output format: human (default) or json |
--cwd <path> |
Working directory |
Use --format json for machine-readable output (useful for CI and editor integrations):
npx contextlint --format json[
{
"file": "docs/requirements.md",
"line": 12,
"severity": "error",
"message": "Required column \"Status\" not found in table",
"ruleId": "TBL-001"
}
]A ready-to-use composite action is included in this repository.
It runs contextlint with --format json and creates inline annotations on PRs.
name: contextlint
on:
pull_request:
paths: ["docs/**"]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nozomi-koborinai/contextlint/.github/actions/contextlint@main
# with:
# config: 'contextlint.config.json' # optional
# files: 'docs/**/*.md' # optional
# version: 'latest' # optionalOr run directly:
- run: npx @contextlint/cliRe-run validation automatically when files change:
npx contextlint --watch
npx contextlint --watch docs/**/*.md
npx contextlint --watch --config contextlint.config.jsonWatch mode runs an initial full lint, then monitors the working directory
for .md file changes. On each change it re-lints all matched files
(required for cross-file rules such as REF-002 and TBL-006), clears the
terminal, and displays the updated results with a timestamp. Rapid changes
are debounced (300 ms). Exit with Ctrl+C.
contextlint can run as an MCP server, allowing AI tools like Claude and Cursor to lint Markdown documents during a conversation.
npm install -D @contextlint/mcp-serverAdd to your mcp.json
(e.g. .cursor/mcp.json or claude_desktop_config.json):
{
"mcpServers": {
"contextlint": {
"command": "npx",
"args": ["@contextlint/mcp-server"]
}
}
}Available tools:
| Tool | Description |
|---|---|
lint |
Lint Markdown content directly with specified rules |
lint-files |
Lint files matching glob patterns using a config file |
context-graph |
Build and return the document dependency graph for the project |
context-slice |
Extract the minimal set of documents relevant to a given query |
impact-analysis |
Analyze which documents are affected by changes to a given file |
compile-context |
Compile document structure into LLM context text |
contextlint ships a Language Server (@contextlint/lsp-server) that
any LSP-capable editor can integrate for in-editor diagnostics, hover
info, and Quick Fixes. The server walks up from the workspace root to
discover contextlint.config.json, matching the CLI / MCP behaviour.
A dedicated VS Code / Cursor extension is tracked separately and will be published to the Marketplace in a future release. For now, the snippets below work in every editor that speaks LSP.
Install:
npm install -D @contextlint/lsp-serverNeovim (with nvim-lspconfig)
-- In your init.lua or after/plugin/contextlint.lua
local configs = require('lspconfig.configs')
local util = require('lspconfig.util')
if not configs.contextlint then
configs.contextlint = {
default_config = {
cmd = { 'npx', 'contextlint-lsp' },
filetypes = { 'markdown' },
root_dir = util.root_pattern('contextlint.config.json', '.git'),
single_file_support = false,
},
}
end
require('lspconfig').contextlint.setup({})[language-server.contextlint]
command = "npx"
args = ["contextlint-lsp"]
[[language]]
name = "markdown"
language-servers = ["contextlint"]JetBrains IDEs (via LSP4IJ)
- Install the LSP4IJ plugin from the Marketplace.
- Open Settings β Languages & Frameworks β Language Servers β Add:
- Name:
contextlint - Command:
npx contextlint-lsp - Mapping β File name patterns:
*.md
- Name:
- Diagnostics cover both document-scope rules (TBL-002, CHK-001, β¦) and project-scope rules (REF-001, REF-002, TBL-006, GRP-001/002/003, CTX-002, β¦) across the entire workspace, in real time as you edit.
- Quick Fixes are available for CHK-001 (check the list item) and
TBL-002 (insert a
TODOplaceholder). - After external file changes (e.g.
git pull), reload the editor window to refresh the workspace cache.
@contextlint/core exposes a Context Graph API for analysing
document dependencies programmatically. This is useful for building
tools that need to understand how Markdown documents relate to each
other.
import {
parseDocument,
buildContextGraph,
getImpactSet,
getContextSlice,
topologicalSort,
getComponents,
classifyImpact,
compileContext,
} from "@contextlint/core";
import type { ContextGraph, GraphNode, GraphEdge } from "@contextlint/core";| Function | Description |
|---|---|
buildContextGraph(documents) |
Build a dependency graph from parsed documents |
getImpactSet(graph, filePath) |
Get all files affected by changing a given file (direct + transitive) |
getContextSlice(graph, documents, query, maxDepth?) |
Get the minimal set of files relevant to a query (file path or ID) |
topologicalSort(graph) |
Topological sort of the document graph (dependency order) |
getComponents(graph) |
Get connected components (clusters of related files) |
classifyImpact(graph, filePath) |
Classify impact as direct vs transitive |
compileContext(patterns, config, cwd) |
Compile documents + config into SKILL.md content |
Example:
import { readFileSync } from "node:fs";
import { parseDocument, buildContextGraph, getImpactSet } from "@contextlint/core";
// Parse documents
const documents = new Map();
documents.set("docs/overview.md", parseDocument(readFileSync("docs/overview.md", "utf-8")));
documents.set("docs/requirements.md", parseDocument(readFileSync("docs/requirements.md", "utf-8")));
documents.set("docs/design.md", parseDocument(readFileSync("docs/design.md", "utf-8")));
// Build the graph
const graph = buildContextGraph(documents);
// What files are impacted if requirements.md changes?
const impacted = getImpactSet(graph, "docs/requirements.md");
// => ["docs/design.md", "docs/overview.md"]The Context Compiler is a deterministic pipeline that transforms your
documents and config into a SKILL.md file for
Claude Code custom skills.
Same config + same documents always produces the same output -- no LLM
involved.
- Load documents matching
includepatterns - Build the dependency graph and classify each document's role (entry, hub, leaf, bridge, isolated)
- Extract document profiles (outline, table schemas, references)
- Describe active lint rules in natural language
- Synthesize everything into a single SKILL.md
Add a compile section to contextlint.config.json:
{
"include": ["docs/**/*.md"],
"compile": {
"skill": {
"name": "my-project-docs",
"description": "Validate and maintain project documentation"
},
"outdir": ".claude/skills/my-project",
"sections": {
"architecture": true,
"rules": true,
"dependencies": true,
"workflow": true
}
},
"rules": []
}| Field | Description |
|---|---|
skill.name |
Skill name for SKILL.md frontmatter (required) |
skill.description |
Skill description for SKILL.md frontmatter (required) |
outdir |
Output directory (default: .claude/skills/contextlint) |
sections.architecture |
Include architecture overview |
sections.rules |
Include active lint rules |
sections.dependencies |
Include dependency graph |
sections.workflow |
Include workflow instructions |
# Generate SKILL.md
contextlint compile
# Preview without writing
contextlint compile --dry-run
# Custom output directory
contextlint compile --outdir .claude/skills/my-skillAdd to your CI pipeline to keep SKILL.md in sync with documentation:
- run: npx contextlint compile --dry-run| Package | Description |
|---|---|
@contextlint/core |
Rule engine and Markdown parser |
@contextlint/cli |
CLI entry point (contextlint command) |
@contextlint/mcp-server |
MCP server for AI tool integration |

{ "$schema": "https://raw.githubusercontent.com/nozomi-koborinai/contextlint/main/schema.json", // Default file patterns (used when no files are specified on CLI) "include": ["docs/**/*.md"], "rules": [ // TBL-001: Required columns must exist in tables { "rule": "tbl001", "options": { "requiredColumns": ["ID", "Status", "Description"], "files": "**/requirements.md" } }, // TBL-002: Key columns must not have empty cells { "rule": "tbl002", "options": { "columns": ["ID", "Status"], "files": "**/requirements.md" } }, // TBL-003: Column values must be from an allowed set { "rule": "tbl003", "options": { "column": "Status", "values": ["draft", "review", "stable"], "files": "**/requirements.md" } }, // TBL-004: Cell values must match a regex pattern { "rule": "tbl004", "options": { "column": "ID", "pattern": "^[A-Z]+-[A-Z]+-\\d{2}$", "files": "**/requirements.md" } }, // TBL-005: When a condition on one column is met, another column must satisfy a constraint { "rule": "tbl005", "options": { "when": { "column": "Status", "equals": "Done" }, "then": { "column": "Date", "notEmpty": true } } }, // TBL-006: IDs must be unique across all matched files { "rule": "tbl006", "options": { "files": "**/requirements.md", "column": "ID" } }, // SEC-001: Required sections must exist in the document { "rule": "sec001", "options": { "sections": ["Overview", "Requirements"], "files": "**/overview.md" } }, // SEC-002: Sections must appear in the specified order // Basic β check order across the whole file: { "rule": "sec002", "options": { "order": ["Overview", "Requirements", "Design"] } }, // With level β group by parent heading and check each group independently: { "rule": "sec002", "options": { "order": ["Overview", "Requirements", "Design"], "level": 3, "files": "**/spec.md" } }, // With section β scope to a specific parent group only: { "rule": "sec002", "options": { "order": ["Endpoints", "Error Handling"], "level": 3, "section": "API" } }, // STR-001: Required files must exist in the project { "rule": "str001", "options": { "files": ["docs/overview.md", "docs/requirements.md"] } }, // CHK-001: All checklist items must be checked { "rule": "chk001", "options": { "section": "Review Checklist", "files": "docs/reviews/*.md" } }, // CTX-001: Sections must contain meaningful content, not placeholders { "rule": "ctx001", "options": { "section": "Overview", "files": "docs/**/*.md" } }, // REF-001: Relative Markdown links must point to existing files { "rule": "ref001", "options": { "exclude": ["_references/**"] } }, // REF-002: Defined IDs must be referenced; referenced IDs must exist { "rule": "ref002", "options": { "definitions": "**/requirements.md", "references": ["**/design.md", "**/overview.md"], "idColumn": "ID", "idPattern": "^REQ-" } }, // REF-003: An item's stability must not exceed the stability of items it depends on { "rule": "ref003", "options": { "stabilityColumn": "Status", "stabilityOrder": ["draft", "review", "stable"], "definitions": "**/requirements.md", "references": ["**/design.md"] } }, // REF-004: Cross-zone links must be declared in the zone's overview { "rule": "ref004", "options": { "zonesDir": "docs/zones" } }, // REF-005: Anchor fragments must point to headings that exist in the target file { "rule": "ref005", "options": { "files": "docs/**/*.md" } }, // REF-006: Image references must point to files that exist { "rule": "ref006", "options": { "exclude": ["*.svg"] } }, // GRP-001: Every ID must be traceable through all stages of the document chain { "rule": "grp001", "options": { "chain": [ { "stage": "Requirements", "files": "**/requirements.md", "idColumn": "ID" }, { "stage": "Design", "files": "**/design.md", "refColumn": "Requirement" }, { "stage": "Test", "files": "**/test-plan.md", "refColumn": "Covers" } ], "idPattern": "^REQ-\\d{3}$" } }, // GRP-002: Document reference graph must be acyclic (no circular references) { "rule": "grp002", "options": { "files": "docs/**/*.md", "exclude": ["CHANGELOG.md"] } }, // GRP-003: Every document must have at least one incoming reference { "rule": "grp003", "options": { "files": "docs/**/*.md", "entryPoints": ["README.md", "index.md"] } } ], // Context Compiler: generate SKILL.md for Claude Code "compile": { "skill": { "name": "my-project", "description": "Validate and maintain project documentation" }, "outdir": ".claude/skills/my-project", "sections": { "architecture": true, "rules": true, "dependencies": true, "workflow": true } } }