---
icon: lucide/shield-check
---

<!-- SPDX-FileCopyrightText: 2026 PythonWoods <dev@pythonwoods.dev> -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# Checks Reference

Zenzic runs six independent checks. Each addresses a distinct category of documentation rot — the slow degradation that happens when a project grows and documentation maintenance falls behind development.

<div class="grid cards" markdown>

- :lucide-link-2-off: &nbsp; __Links__

    Broken internal links, dead anchors, and external URLs that return errors.

    [`zenzic check links`](#links)

- :lucide-file: &nbsp; __Orphans__

    `.md` files present on disk but absent from the site navigation.

    [`zenzic check orphans`](#orphans)

- :lucide-code: &nbsp; __Snippets__

    Python code blocks that fail to compile — caught before readers copy-paste them.

    [`zenzic check snippets`](#snippets)

- :lucide-pencil: &nbsp; __Placeholders__

    Stub pages with too few words or forbidden patterns like `TODO`, `WIP`, `coming soon`.

    [`zenzic check placeholders`](#placeholders)

- :lucide-image: &nbsp; __Assets__

    Media files that exist on disk but are never referenced. __Supports autofix.__

    [`check assets`](#assets) &nbsp;&bull;&nbsp; [`clean assets`](usage/commands.md#autofix-cleanup)

- :lucide-shield-check: &nbsp; __References__

    Dangling reference links, dead definitions, and leaked credentials (exit code 2).

    [`zenzic check references`](usage/advanced.md#reference-integrity-v020)

</div>

---

## Links

__CLI:__ `zenzic check links [--strict]`

Link rot is one of the most common and most visible documentation failures. A developer renames a page, moves a section, or deletes an anchor, and the links that pointed to it silently become dead ends.

`zenzic check links` uses a native Python parser — no subprocesses, no build driver dependency. It scans every `.md` file under `docs/`, extracts all Markdown links with a fenced-block-aware state machine, and validates them in two tiers.

__Tier 1 — internal links (always checked):__

Relative and site-absolute paths are resolved against the `docs/` directory in memory. The target file must exist in the scanned file set. Extension-less paths (`setup`) and directory-index paths (`setup/`) are also resolved. If the link includes a `#fragment`, Zenzic extracts heading anchors from the target file and verifies the fragment matches.

- `[text](missing-page.md)` → target file not found
- `[text](page.md#missing-anchor)` → anchor not found in target

All `.md` files are read once; anchors are pre-computed from headings (`# Heading` → `#heading`). No additional I/O per link.

__Tier 2 — external links (`--strict` only):__

With `--strict`, every `http://` and `https://` URL in the docs is validated via concurrent HTTP HEAD requests using `httpx`. Up to 20 connections run simultaneously. Servers that reject HEAD receive a GET fallback. The same URL referenced in multiple pages is pinged exactly once.

Servers returning `401`, `403`, or `429` are treated as reachable — these indicate access restrictions, not broken links. Timeouts (>10 s) and connection errors are reported as failures.

__What is never validated:__

- Links inside fenced code blocks or inline code spans — the extractor skips them
- `mailto:`, `data:`, `ftp:`, `tel:` and similar non-HTTP schemes
- Pure same-page anchors (`#section`) — not validated by default; enable with
  `validate_same_page_anchors = true` in `zenzic.toml` (see note below)

__`--strict` flag:__ without `--strict`, only internal links are checked (fast, no network). With `--strict`, external HTTP/HTTPS links are also validated via concurrent network requests.

__Same-page anchor validation:__ by default, links like `[text](#section)` that point to a
heading within the same file are not validated. This is intentional — anchor IDs can also
be generated by HTML attributes, custom plugins, or build-time macros invisible at source
scan time. To enable heading-based same-page anchor validation:

```toml
# zenzic.toml
validate_same_page_anchors = true
```

When enabled, every `#fragment` anchor in a same-page link is checked against the headings
extracted from the source file. A link to a non-existent heading slug is reported as broken.

__Why CLI:__ the native link extractor operates on raw Markdown source files. Source-level analysis gives reproducible output independent of the build driver.

__Example output:__

```text
BROKEN LINKS (3):
  index.md:12: 'setup.md' not found in docs
  guide.md:47: anchor '#installation' not found in 'setup.md'
  api.md:88: external link 'https://api.example.com/v2' returned HTTP 404
```

---

## Orphans

__CLI:__ `zenzic check orphans`

An orphan page exists on disk but is not listed in the site navigation. It is invisible to readers who follow the nav tree — it can only be reached by guessing the URL or finding a direct link. Orphans are typically created when a page is added during development but its nav entry is forgotten, or when a nav entry is removed without deleting the corresponding file.

__CLI behaviour:__ scans `docs_dir` for all `.md` files, parses the `nav` from `mkdocs.yml` or `zensical.toml`, and reports the set difference. Directories listed in `excluded_dirs` (default: `includes`, `assets`, `stylesheets`, `overrides`, `hooks`) are skipped entirely. Symlinks are ignored.

__What it catches:__

- Pages created on disk but never added to `nav`
- Pages whose `nav` entry was removed without deleting the file

__Example output:__

```text
ORPHANS (2):
  api/experimental.md
  guides/draft-tutorial.md
```

---

## Snippets

__CLI:__ `zenzic check snippets`

Code examples in documentation are tested less rigorously than production code. A snippet that worked when it was written may have a syntax error introduced by a refactor, a copy-paste mistake, or a manual edit that was never reviewed. Readers who copy broken code waste time debugging errors that have nothing to do with their actual problem.

`zenzic check snippets` validates the syntax of fenced code blocks using pure-Python parsers — no subprocesses are spawned for any language.

__Supported languages:__

| Language tag | Parser | What is checked |
| :--- | :--- | :--- |
| `` python ``, `` py `` | `compile()` in `exec` mode | Python 3.11+ syntax |
| `` yaml ``, `` yml `` | `yaml.safe_load()` | YAML 1.1 structure |
| `` json `` | `json.loads()` | JSON syntax |
| `` toml `` | `tomllib.loads()` (stdlib 3.11+) | TOML v1.0 syntax |

Blocks tagged with any other language (`` bash ``, `` javascript ``, `` mermaid ``, etc.) are treated as plain text and are not syntax-checked. However, __every fenced block is still scanned by the Zenzic Shield__ for credential patterns — syntax validation and security scanning are independent.

__CLI behaviour:__ walks `docs_dir`, reads each `.md` file, and calls `check_snippet_content(text, file_path, config)` on the raw content.

__Block extraction:__ Zenzic uses a deterministic line-by-line state machine rather than a regex to extract code blocks. This prevents false positives from inline code spans (e.g., `` ` ```python ` `` in prose text) and is robust against `pymdownx.superfences` documents with interleaved Mermaid or other custom fences. See [Architecture — State-machine parsing](architecture.md#state-machine-parsing-and-superfences-false-positives) for details.

__What it catches:__

- Python: `SyntaxError` — missing colons, unmatched brackets, invalid expressions; parser crashes (`MemoryError`, `RecursionError`)
- YAML: structural errors — unclosed sequences, invalid mappings, duplicate keys
- JSON: `JSONDecodeError` — trailing commas, missing quotes, unmatched brackets
- TOML: `TOMLDecodeError` — missing quotes on values, invalid key syntax, type mismatches

__What it does not catch:__

- Runtime errors (`NameError`, `TypeError`, `ImportError`, etc.) — only syntax is checked
- Intentionally incomplete snippets — fragments, ellipsis stubs, pseudo-code
- Bash, JavaScript, or any other language without a pure-Python parser

__Tuning:__ use `snippet_min_lines` in `zenzic.toml` to skip short blocks. The default of `1` checks everything including single-line blocks. Set it to `3` or higher to ignore import stubs and one-liners that are likely illustrative rather than executable.

__Example output:__

```text
INVALID SNIPPETS (3):
  tutorial.md:48 - SyntaxError in Python snippet — expected ':'
  config/reference.md:22 - SyntaxError in YAML snippet — mapping values are not allowed here
  api/reference.md:112 - SyntaxError in JSON snippet — Expecting property name enclosed in double quotes
```

---

## Placeholders

__CLI:__ `zenzic check placeholders`

Placeholder pages are pages that were created as stubs and never completed. They are a form of documentation debt: they appear in the nav, they show up in search results, but they contain nothing useful. Readers who land on them lose trust in the documentation as a whole.

`zenzic check placeholders` applies two independent signals to detect unfinished content.

__Signal 1 — word count:__ pages with fewer than `placeholder_max_words` words (default: 50) are flagged as `short-content`. Word count is computed by splitting the raw Markdown source on whitespace and includes headings, link text, and code block content. A page with only a title heading and two lines of prose will typically be below this threshold.

__Signal 2 — pattern match:__ lines containing any string from `placeholder_patterns` (case-insensitive, default: `coming soon`, `work in progress`, `wip`, `todo`, `to do`, `stub`, `placeholder`, `fixme`, `tbd`, `to be written`, `to be completed`, `to be added`, `under construction`, `not yet written`, `draft`, `da completare`, `in costruzione`, `in lavorazione`, `da scrivere`, `da aggiungere`, `bozza`, `prossimamente`) are flagged as `placeholder-text`. Pattern matching is performed line-by-line on the raw Markdown source.

Both signals are independent. A page may trigger one, both, or neither.

__CLI behaviour:__ reads each `.md` file and calls `check_placeholder_content(text, file_path, config)`.

__Tuning:__

```text
# zenzic.toml

# Raise the threshold for projects with dense, concise pages
placeholder_max_words = 100

# Customise patterns for your team's conventions
placeholder_patterns = ["coming soon", "wip", "fixme", "tbd", "draft"]

# Disable word-count check entirely (pattern check still runs)
placeholder_max_words = 0

# Disable both checks
placeholder_max_words = 0
placeholder_patterns = []
```

__Example output:__

```text
PLACEHOLDERS/STUBS (3):
  guides/advanced.md:1 [short-content] - Page has only 12 words (minimum 50).
  api/webhooks.md:7 [placeholder-text] - Found placeholder text matching pattern: 'coming soon'
  api/webhooks.md:1 [short-content] - Page has only 8 words (minimum 50).
```

---

## Assets

__CLI:__

- `zenzic check assets` — Controlla la presenza di file non utilizzati.
- `zenzic clean assets` — Rimuove in modo sicuro gli asset non utilizzati.

!!! tip "Autofix available"
    Use `zenzic clean assets` to automatically delete any unused assets found by this check. You will be prompted to confirm the deletion (`[y/N]`), or you can pass `-y` to skip the prompt. Use `--dry-run` to preview the files that would be deleted without actually deleting them. Zenzic will never delete files that match your `excluded_assets`, `excluded_dirs`, or `excluded_build_artifacts` patterns.

Unused assets are files that exist in the documentation source directory but are never referenced by any page. They are typically left over after a page is renamed, an image is replaced, or a section is restructured. They do not cause visible errors, but they accumulate over time and bloat the built site.

__What counts as "used":__ an asset is considered used if it appears as a Markdown image link (`![alt](path)`) or an HTML `<img src="...">` tag in any `.md` file. Paths are normalised using POSIX path arithmetic so that relative references like `../assets/logo.png` from a subdirectory correctly resolve to `assets/logo.png` relative to the docs root.

__Always excluded from the check:__ `.css`, `.js`, `.yml` files are always considered intentionally present and never reported as unused, even if no page links to them. These are typically theme overrides or build configuration files.

__CLI behaviour:__

1. Collects all non-`.md`, non-excluded files from `docs_dir` recursively
2. Reads every `.md` file and extracts referenced asset paths via `check_asset_references(text, page_dir)`
3. Reports `calculate_unused_assets(all_assets, used_assets)` — the set difference

__What it catches:__

- Screenshots uploaded but never embedded in any page
- Images left over after a page reorganisation or rename
- Attachments (PDFs, data files) that were linked from a page that no longer exists

__Example output:__

```text
UNUSED ASSETS (3):
  assets/old-screenshot.png
  assets/diagram-v1.svg
  attachments/deprecated-spec.pdf
```
