selfdoc v0.37.1 /Check Guide
On this page

Run selfdoc check to validate directives, measure coverage, execute marked examples, spell-check page prose against the vendored word list and the shared accept list, lint blog posts alongside documentation pages, and apply every registered lint rule with its declared severity -- suppressing warnings only, never errors.

#Check Guide

selfdoc check is your documentation linter. It validates every directive in your templates, measures how much of your public API is documented, runs SEO best-practice checks, and detects stale descriptions. Run it locally before pushing, or wire it into CI for automated enforcement.

#What It Does

A single selfdoc check run performs three categories of analysis that together cover directive correctness, API documentation coverage, and SEO best practices. Each category produces structured output with file paths, line numbers, and actionable messages:

  1. Directive validation -- resolves every directive marker in your docs/ templates and reports whether each one succeeds or fails.
  2. Coverage analysis -- counts public/exported symbols in your source code and checks how many are referenced by directives.
  3. SEO linting -- scans templates for heading structure, meta description, alt text, contrast ratio, and other best practices.
$_ bash
selfdoc check

#Directive Validation

Every directive in your docs is resolved against the actual source code. If a module path is wrong, a target symbol does not exist, or a custom directive script throws an error, the check reports it with file and line number:

Directives
  index.md:12  ref path="mypackage"        OK
  api.md:8     ref path="mypackage.core"    OK
  api.md:20    table-schema path="bad.path" FAILED: Module not found

Fix failures by correcting the directive's path or target attribute to match your actual source code.

#Coverage

Coverage measures how much of your public API surface is documented. selfdoc walks your source directories, extracts public symbols using the language-specific extractor (Python, Go, or TypeScript), and checks whether each symbol appears in the resolved content of a directive.

Coverage: 15/23 public symbols documented (65%)
Undocumented symbols:
  mypackage/core.py: helper_function, InternalConfig

#Coverage threshold

Set min_coverage in your selfdoc.json to enforce a minimum percentage of public symbols that must be referenced by directives. If coverage falls below this threshold, selfdoc check exits with code 1, making it useful as a CI gate to prevent coverage regressions over time:

{} json
{
  "min_coverage": 80
}

If coverage falls below this value, selfdoc check exits with code 1. Useful in CI to prevent coverage regressions.

#Excluding modules

Modules listed in gen.exclude are excluded from both coverage calculations and auto-generated documentation pages. Use this for intentionally-internal modules, test helpers, or implementation details that should never appear in public-facing documentation. Excluded symbols do not count against your coverage threshold:

{} json
{
  "gen": {
    "exclude": ["mypackage._internal", "mypackage.tests"]
  }
}

#Lint Rules

Every selfdoc check invocation runs the whole lint registry: SEO and page structure, description staleness and source drift, cross-references and symbol documentation, example validation, CLI reference completeness, version consistency, blog posts, and unified sites. Each rule has a unique code, a severity, and an actionable message explaining what is wrong and how to fix it. Errors cause a non-zero exit; warnings are informational. The table below is generated from selfdoc_core/lints.toml, the one place a code and its severity are declared.

Lint Rules
CodeSeverityWhat it checks
SEO001errorMultiple H1 headings on a page. Use a single # heading.
SEO002warningHeading level gaps (e.g., H2 followed by H4 skipping H3).
SEO003warningImage with empty alt text (![](...)). Add descriptive alt text.
SEO004warningPage title exceeds 60 characters (combined with project name). Shorten it.
SEO006errorMissing description in frontmatter. Add one for meta tags.
SEO007warningFirst paragraph after a heading is outside the 30-80 word range. Every page type is held to the same band, generated pages included.
SEO008warningLow numeric data density. Pages with 200+ words should include concrete quantities; version strings and calendar years do not count.
SEO009warningDescription is shorter than 120 characters. Aim for 120-155.
SEO010warningFrontmatter description exceeds 155 characters. Trim it.
SEO011warningEmpty heading section (heading followed by another heading with no content between).
SEO012warningWCAG contrast ratio below threshold for theme colors. Fix in CSS custom properties.
SEO013errorNo title source: neither frontmatter title nor an H1 heading exists on the page.
SEO014warningMeaningless image alt text (e.g., "image", "screenshot", or a bare filename). Write something descriptive.
SEO015warningGeneric anchor text like "click here" or "read more". Use descriptive link text.
STALE001errorPage content changed but frontmatter description was not updated. Review and update the description.
STALE002warningManifest and disk disagree: a page or post exists on disk but is missing from .selfdoc/manifest.json, or the manifest lists one that is gone. Run selfdoc gen.
DRIFT001errorThe source docstrings (or CLI schema) a page documents changed while its description did not. Update the description, or run selfdoc baseline accept <page> if it is still accurate.
DQ001warningThe frontmatter description restates the page or symbol name instead of describing it.
DQ002warningFrontmatter description is shorter than 20 characters.
DQ003warningA page carrying a ref directive has a description shorter than 30 characters.
XREF001warningA Markdown link points at a .md page that does not exist in the docs tree.
XREF002errorA directive's path resolves but names a file that is not on disk.
PARAM001warningA referenced symbol has a parameter its docstring never documents.
RETURN001warningA referenced symbol returns a value its docstring never documents.
EXAMPLE001warningA Python or JSON code block does not parse. Fix the snippet's syntax.
EXAMPLE002errorA code block marked validate failed its configured validator. The message carries the validator's exit code and output tail.
EXAMPLE003errorA code block is marked validate but no examples command is configured for its language. Add one, or drop the marker.
CLI001warningstrictcli project: a CLI reference page is missing for a command, or a flag in the schema is not documented on its page.
CLI002warningstrictcli project: a command, group, or flag help text is shorter than 50 characters.
LANG001errorA configured source entry names a language selfdoc has no extractor for.
SEARCH001errorpagefind is not installed, so the build cannot index the site.
VER001errorA version listed in versions could not be extracted from its git tag, so it could not be validated.
VER002errorversion in selfdoc.json does not match the version detected from the project manifest (pyproject.toml, package.json, or a VERSION file).
VER003errorThe last entry of the versions array does not match version in selfdoc.json.
VER004errorA generated root file that embeds project.version does not contain the expected version. Regenerate with selfdoc gen --version-override <v>.
SPELL001errorA word in page prose is in neither the vendored English word list nor the accept list. Fix the misspelling, or add the term to ~/Projects/ark/spelling-accept.txt if it is genuine.
POST001errorA post is missing the required date field in its frontmatter.
POST002errorA post is missing the required title field in its frontmatter.
POST003errorA post's date is not written as YYYY-MM-DD.
POST004errorTwo posts resolve to the same slug.
POST005errorA published post's slug changed, which would break its permalink.
POST006errorA post is missing the required directives declaration, or declares something other than true/false. Every post states whether it may carry directive markers; there is no default.
POST007errorA post declaring directives: false carries a directive marker. The message names the marker and the line it sits on.
LINK001errorAn emitted reference -- a link, a canonical, a sitemap entry or a feed link -- names a file the build did not write.
UNIFIED001errorA project listed in the unified section has no selfdoc.json.
UNIFIED002errorA constituent project, or the docs-site's own content, could not be checked.

#Spelling (SPELL001)

Every documentation page and every published post is spell-checked against a vendored English word list of about 172,000 words -- a pinned snapshot of the English Speller Database at its large size, carrying US, British and Canadian spellings, so colour and color are equally correct. The list ships as package data beside upstream's copyright notice, which travels with it as redistribution requires.

Structure comes from the block tokenizer, so fenced code blocks and directive blocks are never scanned; inline code spans, link destinations, URLs, and directive markers are blanked before a line is read. Tokens that look like machinery rather than English -- anything carrying a digit, an underscore, a slash, a dotted qualified name, or an interior capital such as parseConfig -- are skipped whole. Hyphenated compounds are checked part by part, and a possessive is accepted from its base word. Each finding names the file, the line, the column, and an edit-distance-one suggestion when one exists.

Genuine terms the general word list cannot know -- project names, tool names, technical vocabulary -- belong on the accept list at ~/Projects/ark/spelling-accept.txt: one lowercase word per line, # starts a comment, and a word there is accepted in any casing, everywhere. A missing file simply means nothing has been accepted yet. A file that exists but holds a line that is not a bare word is a hard error, so a malformed list is never read as a shorter one.

SPELL001 is error severity and cannot be suppressed. Fixing the prose or accepting the term are the two available answers, which is the point: a misspelling on a published page is a defect, and the accept list records the deliberate decision that a word is not one.

To seed the accept list across a machine, selfdoc spell-corpus runs the same engine over every selfdoc project sitting beside this one and prints each project's unknown words with a first location. It is strictly read-only over the projects it visits.

#Suppressing rules

Suppress specific lint rules globally in your config or per invocation via CLI flags. Both sources are merged, so you can set baseline suppressions in config and add per-run overrides as needed. Use suppression sparingly since each rule catches real SEO or accessibility issues:

{} json
{
  "lint_ignore": ["SEO007", "SEO008"]
}

Or per invocation with --ignore:

$_ bash
selfdoc check --ignore SEO007,SEO008

Both sources are combined -- CLI flags and config are merged.

Suppression reaches warning-severity codes only. Naming an error-severity code -- in lint_ignore or in --ignore -- is a hard error that names the code and its severity, and the run stops before any checking happens. An error says the build is wrong: a broken emitted reference, a missing description, a post whose slug moved. Silencing it hides the defect instead of resolving it, which is how a genuinely broken build once passed its own check. Fix the defect, or change the rule's severity in the registry if the rule itself is wrong.

#Staleness Detection

selfdoc tracks SHA-256 hashes of each page's resolved content and frontmatter description. When the content changes but the description stays the same, it raises a STALE001 error. This catches the common case where you update a page's content but forget to revise the description that feeds into meta tags and search results.

Hashes are stored in .selfdoc/hashes/hashes.json and auto-committed after each check (unless you pass --no-auto-commit or --dry-run).

#Example Validation

Every Python and JSON code block is parsed during selfdoc check, and a block that does not parse raises EXAMPLE001. Parsing proves only that a snippet is well-formed, not that it still works: an example calling a function you renamed six months ago parses perfectly and is completely wrong. To catch that class, mark the block validate and configure a validator for its language:

`markdown

from mylib import greet

print(greet("world"))

{} json
{
  "examples": {
    "python": "uv run --directory python python {file}",
    "go": "scripts/validate-example-go.sh {file}",
    "ts": "scripts/validate-example-ts.sh {file}"
  }
}

selfdoc writes each marked block to a scratch file suffixed for its language, substitutes the path for {file}, and runs the command from the project root with a 60-second timeout. A non-zero exit becomes an EXAMPLE002 error naming the exit code and the last five lines of the validator's output. A marked block whose language has no configured command becomes an EXAMPLE003 error rather than being skipped, so an unhonored marker can never masquerade as a passing one.

The marker is opt-in per block: unmarked blocks are never executed and keep the EXAMPLE001 syntax check exactly as before. Validators run without a sandbox, so configure commands that compile, type-check, and register rather than ones that execute arbitrary payloads.

#Output Formats

#Text (default)

Human-readable output with colored status indicators, file paths, line numbers, and rule codes. This is the default format designed for local development where you read the output directly in a terminal and fix issues one by one:

$_ bash
selfdoc check

#JSON

Machine-readable output for CI integration, custom tooling, or programmatic analysis. The JSON format includes the same information as text output but structured as arrays of objects with consistent field names for easy parsing:

$_ bash
selfdoc check --format json

Returns a JSON object with directives, coverage, lints, and exit_code fields. Example structure:

text
{
  "directives": [{"file": "index.md", "line": 12, "status": "OK", ...}],
  "coverage": {"total_public": 23, "referenced": 15, ...},
  "lints": [{"code": "SEO006", "severity": "error", ...}],
  "exit_code": 0
}

#Exit Codes

selfdoc check uses standard exit codes to signal pass or fail, making it safe to use as a CI gate or pre-commit hook. It exits with code 0 when everything passes, and code 1 when any of these conditions are true:

  • A directive resolution failed
  • Any lint has severity error
  • Coverage is below the min_coverage threshold

Warnings alone do not cause a non-zero exit. This makes it safe to use in CI -- warnings are informational, errors block the pipeline.

Tip

Run selfdoc check --dry-run to see staleness results without writing hash files to disk. Useful for previewing what would change.

Next: rlsbl Integration -->

Search