Skip to content

harn docs

Terminal window
harn docs
harn docs meta <path> [key] [--json]
harn docs metadata validate [--repo <name>] [--cached]
harn docs metadata sync [files...] [--repo <name>] [--check] [--cached] [--reviewed]
harn docs new <type> <path> --summary <text> --owner <id> [--status <status>] [--severity <severity>]
harn docs lint [--fast] [--repo <name>] [--cached]
harn docs links [--repo <name>] [--no-fragments] [--strict] [--check-escapes] [--fail]
harn docs sweep [--repo <name>]
harn docs index [--dry-run] [--repo <name>]

The docs command group gives an embedding host one documentation maintenance surface. The host supplies its repository root, submodule list, exclusions, and any project-specific root allowlist through HarneryProgramContext.

docs meta reads a markdown file’s leading YAML block:

Terminal window
harn docs meta docs/plans/cache-redesign.md
# {"status":"in-progress","date":"2026-07-13","tags":["cache"]}
harn docs meta docs/plans/cache-redesign.md status
# in-progress

Relative paths resolve from the configured repository root. Absolute paths also work. The command exits nonzero when the file, frontmatter block, or requested top-level key is missing. A requested scalar prints plainly in an interactive terminal; piped output and --json use JSON.

docs metadata validate checks every managed document. Managed files must use schema: harnery-doc/v2; legacy or missing metadata is an error:

Terminal window
harn docs metadata validate --repo .

The report separates valid, invalid, legacy, and missing files. Validation checks canonical UTC timestamps, timestamp order, document types, lifecycle status, owner ids, summaries, tags, relationships, access grants, and the fields required by each profile.

docs metadata sync updates timestamps for staged Markdown changes, or for the explicit files passed on the command line:

Terminal window
harn docs metadata sync
harn docs metadata sync docs/runbook.md --reviewed
harn docs metadata sync --check

It advances updated_at for semantic metadata or body changes and advances status_changed_at only when status changes. created_at is immutable. --reviewed records a runbook review and sets its next review 180 days later. Use --check in hooks or CI to reject timestamp drift without writing. Add --cached when the check must read the staged blobs from Git rather than the working tree. Cached synchronization is read-only and requires --check.

docs new scaffolds valid v2 metadata for a plan, issue, handoff, runbook, or topic. It refuses to overwrite an existing file and validates the requested type, status, owner, and severity before writing.

docs links resolves every relative Markdown target in the tracked docs and validates the heading fragment each one points at:

Terminal window
harn docs links --repo .
harn docs links --format json | jq '.findings[] | select(.rule == "missing-fragment")'

It reports four rules. missing-target is a path that does not exist. missing-fragment is a target that exists while the #heading after it does not, which is the failure a plain file-existence check cannot see: the reader lands silently at the top of the page. case-mismatch is a target that differs from the file on disk only by letter case, so it resolves on macOS and breaks on Linux; the finding carries the on-disk path as suggestion. escapes-repo is opt-in via --check-escapes and flags a link that resolves on your machine only because a sibling repository happens to sit next to it.

Fragments are matched against GitHub’s anchor rules: heading slugs including the duplicate -1/-2 suffixes, explicit {#custom-id} suffixes, and HTML id or name attributes. Pass --no-fragments to check target existence alone.

A link checker is only useful if its output is worth reading, so these never produce a finding:

  • External URLs, mailto:/tel: schemes, and protocol-relative links.
  • Anything inside a fenced code block or an inline code span, so documented example links are not mistaken for real ones.
  • Targets containing template syntax such as {{slug}}, ${DIR}, or a <placeholder>, which cannot resolve by construction.
  • Root-absolute targets like /pricing, which are far more often site routes than repository paths.
  • #L42 line references and #/spa/route hash routes, neither of which can name a heading.
  • Everything under the host’s configured documentation exclusions.

Findings in documents that record a past state — anything under archive/, audits/, changelogs/, handoffs/, or decisions/ — are reported as warnings rather than errors, because a document naming a path as it existed then is accurate rather than broken. The same downgrade applies to lifecycle docs (under issues/, plans/, or handoffs/) whose YAML frontmatter carries a terminal status: (resolved, wontfix, shipped, abandoned): a resolved issue is a settled record, while an open one keeps error severity because its guidance is live. --strict reports all of these as errors.

For a single link that must stay as written, put <!-- links-allow: reason --> on its line; for a whole document, put <!-- links-allow-file: reason --> anywhere in it.

docs links exits zero even when it finds errors. Whether link health should block a commit or a CI run is an enforcement decision that belongs to the host project, not a default this command imposes. Pass --fail to exit nonzero when there is at least one error finding.

  • docs lint checks directory layout, filenames, and lifecycle status. --fast skips content checks for pre-commit use.
  • docs lint --cached validates the complete Git-index tree. During a path-scoped commit, Git supplies the proposed partial-commit index, so unrelated working-tree edits cannot change the verdict. Repository-wide entry and naming rules still run.
  • docs sweep reports stalled plans, cold issues and handoffs, stale topic docs, and unverified runbooks.
  • docs index rebuilds marker-delimited audit and issue index tables.
  • Bare docs reports markdown freshness from Git history.

Lint, sweep, and index read lifecycle status: only from strict harnery-doc/v2 frontmatter. Legacy metadata is not read or normalized.