hdlinter
v0.2.1
Published
Cursor/VS Code extension and CLI for validating project document metadata headers.
Maintainers
Readme
hdlinter
Cursor/VS Code extension and CLI for validating Markdown planning headers.
The linter enforces this header shape at the top of every Markdown file:
- Status: Draft
- Owner: Project Manager
- Reviewers: Siem, Vansh
- Last updated: 2026-05-11
- Milestone: M1
- Depends on: [Execution Plan](docs/mvp-execution-plan.md) [2026-05-10]
- Supersedes: NoneRules
- Required fields must appear in order.
- Header fields are written as unordered list items so GitHub renders them as
separate lines without raw
<br>tags. Statusmust be one ofDraft,In Review,Approved, orSuperseded.Last updatedmust be a valid ISO date.Depends onandSupersedesmust be eitherNoneor Markdown links.- Header links must include the referenced document's
Last updateddate in brackets after the link. - Referenced files must exist when they point at local Markdown files.
- Bare local
.mdpaths in normal document body text must be clickable Markdown links. Inline code, fenced code blocks, images, URLs, and existing links are ignored. Last updatedmust change when the body or stable metadata changes.Last updatedmust not change when only volatile header data changes.- A dependency newer than the current document is flagged as possible drift.
- A superseded document newer than the superseding document is flagged as suspicious.
State And Git
The extension writes hidden graph/hash state to .hdlinter/state.json. That file
is the accepted document baseline, so projects that rely on freshness checks should
commit it to git.
Content freshness is checked from a hash of:
- the post-header body
- stable header fields:
Status,Owner,Reviewers,Milestone, dependency targets, and superseded targets
These are excluded from the freshness hash:
Last updated- dependency and supersedes date annotations such as
[2026-05-11] - list-marker/header formatting changes
This means refreshing dependency dates does not force an unrelated date bump. It also
means a date-only Last updated change is reported and autofixed back to the previous
accepted date.
The state file is intentionally deterministic: it does not store an absolute workspace
path or generated timestamp. hdlinter --write-state writes state only after a clean
lint run. hdlinter --fix fixes documents first, then writes state only if errors are
gone.
Configuration
hdlinter is opt-in per project. The extension only lints a Markdown workspace when
hdlinter.conf.json exists somewhere above the opened folder. Without that marker,
it stays silent.
Create hdlinter.conf.json in the project root:
{
"ignore": ["docs/initial/**", "vendor/**/*.md"],
"allowMissingHeaders": false
}ignoreskips workspace-relative Markdown paths matched by glob.allowMissingHeaderslets freeform Markdown files exist without a metadata header. The CLI summary reports how many Markdown files were allowed through this path so a clean lint is not mistaken for full metadata coverage.- When missing headers are not allowed, Cursor exposes Quick Fixes to either insert a
normalized header or add the file to
ignore. - The config file may be empty. An empty marker means "use defaults."
Optional header schema (see Diagnostics reference):
schemaVersion,header.fields,statuses,fieldAliases,stripUnknownHeaderFieldscustomize field order, validation, and migration aliases.- After schema changes, run
hdlinter --fix(safe reorder + placeholders). Runhdlinter --fix-aggressiveor use the editor quick fix Remove header fields not in schema to drop consecutive header lines whose names are not in the schema. - Any
hdlinter.conf.jsonschema validation error blocks lint and state writes until resolved.
Cursor Setup
From this directory:
npm install
npm run compileThen in Cursor:
- Open this folder.
- Press
F5to launch the extension development host. - In the launched Cursor/VS Code window, open the target Markdown project.
- Diagnostics and Quick Fixes appear for Markdown files.
For regular use, package/install it as a VS Code-compatible extension later:
npm install -g @vscode/vsce
npm run packageThen install the generated .vsix in Cursor.
CLI
Run without installing:
npx hdlinter .When the CLI cannot find hdlinter.conf.json above the current directory, it creates
an empty marker at the nearest package.json or .git root. If neither exists, it
creates the marker in the current directory. That first run opts the project in.
Lint a workspace and rebuild hidden graph state:
npx hdlinter ../aicompliance --write-stateAutofix a workspace (safe structural fixes, body .md path links, and placeholders;
does not remove unknown header lines):
npx hdlinter ../aicompliance --fixSame as --fix, plus strip consecutive header lines not declared in the configured schema:
npx hdlinter ../aicompliance --fix-aggressiveThe CLI exits non-zero when errors are found.
CLI output uses ANSI color when stdout/stderr are terminals. NO_COLOR disables color,
and FORCE_COLOR=1 enables it for redirected output. The final summary includes
configured ignored Markdown files and allowed headerless Markdown files when present.
Local Development
For quick local CLI testing from this repo:
npm install
npm run build
tmpdir="$(mktemp -d)"
printf "" > "$tmpdir/hdlinter.conf.json"
printf -- "- Status: Draft\n- Owner: Test\n- Reviewers: Test\n- Last updated: 2026-05-11\n- Milestone: M0\n- Depends on: None\n- Supersedes: None\n\n# Sample\n" > "$tmpdir/sample.md"
npm exec -- hdlinter "$tmpdir"npm link works for local development, but publishing to npm is the intended path for
normal npx hdlinter usage.
Future Work
See Future Work Plan for the detailed roadmap. The focus is configurability, granular diagnostics/autofixes, and stronger testing. Modularity is treated as an enabling constraint, not a broad refactor goal.
