Health check for a markdown knowledge base (Obsidian-compatible, the LLM-wiki pattern) — personal or shared. Use whenever asked to lint, audit, check, review, or clean up a KB, on any scheduled hygiene/maintenance run, or when KB quality is in doubt (stale pages, contradictions, broken links, unprocessed sources, index drift). Drift is the KB's primary failure mode; if in doubt whether a lint pass is warranted, it is.
Installs into .claude/skills of the current project.
Are you the author of Kmd Lint?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/yasik-kmd-lint)
---
name: kmd-lint
version: 0.3.1
description: Health check for a markdown knowledge base (Obsidian-compatible, the LLM-wiki pattern) — personal or shared. Use whenever asked to lint, audit, check, review, or clean up a KB, on any scheduled hygiene/maintenance run, or when KB quality is in doubt (stale pages, contradictions, broken links, unprocessed sources, index drift). Drift is the KB's primary failure mode; if in doubt whether a lint pass is warranted, it is.
---
# kmd-lint — KB health check
A knowledge base rots by default: indexes drift from pages, claims go stale,
duplicates accumulate, sources pile up un-ingested. Lint is the counter-force
and it is not optional — every production implementation of the LLM-wiki
pattern reports drift as *the* failure mode. Lint runs in two phases:
deterministic checks (a script — free, exhaustive) and judgment passes (you —
expensive, so spent only where scripts can't reach).
## Before you start
Locate the KB root and transport the same way kmd-ingest does (its
`references/obsidian-cli-transport.md` covers the Obsidian CLI, whose
`backlinks`/`orphans`/`unresolved` commands can corroborate graph
findings when available): a path you were given >
`$KMD_ROOT` > `.kmd.json` at the workspace root > a directory containing
`schema.md`/`log.md` > the default `kb/`. The script's `--kb` flag implements
this resolution. Then check for the **org extension**: if `.kmd.json` declares
an `"org"` key or the workspace has `_charter/ORG.md`, read
[references/org-extension.md](references/org-extension.md) — it changes how
findings are routed in Phase 3. Otherwise the core protocol below applies
as written.
## Phase 1 — mechanical checks (run the script first)
```bash
python3 scripts/lint_mechanical.py --kb <kb-root> --report --agent <your-name-or-agent-id>
```
This checks: broken wikilinks, invalid/missing frontmatter (including the
provenance rule), modified files under `sources/` (append-only violation,
via git — when the KB is not under git the script says so explicitly rather
than implying coverage), unreferenced sources (un-ingested intake; a
`skip` log entry closes a source intake reviewed and declined), orphan
pages, page updates with no LOG entry, oversized pages, and a missing or
stale `index.md` (fix: `recompile_index.py` from kmd-ingest). With
`--report` it writes a
report skeleton to `<kb>/.lint/<date>-mechanical.md` (or the `report_dir`
configured in `.kmd.json`) with empty **Judgment findings** and **Resolution**
sections for you to fill in. Use `--format json` to process findings
programmatically.
## Phase 2 — judgment passes (what scripts cannot catch)
Read `index.md` (or list pages) to plan the passes; read full pages only
where a pass flags something. Four passes, in order of value:
1. **Contradictions.** Cluster pages that speak about the same entity/topic
(index descriptions and shared tags are good signals). Compare their
claims. Two pages asserting incompatible facts is the highest-severity
finding lint can produce — a KB that contradicts itself is worse than no
KB, because it answers confidently either way. When you find one, mark
**both** pages with the `> [!contradiction]` callout per schema.md's
framework (each side's claim, what would resolve it) — that marking IS a
direct fix; the resolution itself is an open item unless dates, context,
or primary sources settle it on the spot. The mechanical pass counts
unresolved callouts on every future run.
2. **Staleness.** Pages whose `sources:` have newer material filed in
`sources/` that they don't yet reflect; pages on volatile topics with
old `updated` dates; claims with dates in them that have since passed.
Flag, don't silently rewrite — the fix is a re-ingest against the newer
source.
3. **Near-duplicates.** Pages whose titles/descriptions suggest the same
concept split across two homes (the dedup gate at ingest time misses
some). Propose a merge direction: which page survives, which becomes a
redirect stub or is deleted after its unique content moves.
4. **Confidence downgrades.** Pages marked `confidence: high` whose sources
are thin (single bare URL, no filed source) or contradicted elsewhere.
Downgrading confidence is a cheap, honest fix you can apply directly.
## Phase 3 — triage and resolution
Every finding ends in exactly one of two places (core protocol):
- **Fixed directly** — mechanical, judgment-free repairs: a broken link whose
target was renamed, a missing frontmatter field derivable from content, a
confidence downgrade, an orphan page linked from the obvious related page.
Every fix you apply is itself a KB write: follow the kmd-ingest protocol
(bump `updated`/`author` on touched pages, validate, log with
`--action lint`).
- **Recorded as open items** — anything requiring knowledge the linter
doesn't have (which side of a contradiction is true, whether a merge loses
nuance, whether a stale claim still holds): write it into the report's
**Resolution** section as a concrete, actionable item for the KB owner.
Un-ingested sources are open items too — each is a pending ingest, or a
`kb_log.py --action skip` once someone has reviewed and declined it.
*Org installations route instead of recording* — judgment calls go to page
authors' inboxes, intake to the owning agent. See
[references/org-extension.md](references/org-extension.md); the report still
records every handoff.
Complete the report (all three sections), then log the pass:
```bash
python3 scripts/kb_log.py --action lint --agent <your-name-or-agent-id> \
--title "lint pass: <N> errors, <M> warnings, <K> open items"
```
(`kb_log.py` ships with the kmd-ingest skill; a copy of the shared helpers is
bundled here — the log line matters more than which copy writes it.)
## What lint never does
- Rewrite page *content* wholesale — lint repairs structure and surfaces
judgment; substantive rewrites are re-ingests
- Touch anything under `sources/` (append-only, even for lint) beyond
restoring a tampered file to its committed state
- Hand-edit `index.md` (script-generated) or write log.md by hand
- Silently "fix" a contradiction by picking a side — surface it; the owner
or a re-ingest against sources decides
## Done looks like
Script run clean or findings triaged → judgment passes done over the full
page set → report with all three sections filled → open items concrete enough
to act on (or handoffs delivered, in org mode) → one LOG entry. A lint pass
that only runs the script is half a lint pass.