Configuration Reference
Zenzic is configured through a TOML file. Every field has a sensible default, so zero-config usage is fully supported -- but production projects benefit from explicit tuning.
Config File Priority
Zenzic resolves configuration using a first-match-wins priority chain:
| Priority | Source | Description |
|---|---|---|
| 1 | zenzic.toml | Standalone file at the repository root -- the authoritative sovereign config |
| 2 | pyproject.toml | [tool.zenzic] table inside pyproject.toml |
| 3 | Built-in defaults | Hardcoded defaults when no config file is found |
When a config file is present but contains a TOML syntax error, Zenzic raises a ConfigurationError with a Rich-formatted message. It will never silently fall back to defaults when a file exists but cannot be parsed.
Standalone zenzic.toml
# zenzic.toml — at the repository root
docs_dir = "docs"
snippet_min_lines = 3
strict = true
[build_context]
engine = "mkdocs"
Embedded in pyproject.toml
# pyproject.toml
[tool.zenzic]
docs_dir = "docs"
snippet_min_lines = 3
strict = true
[tool.zenzic.build_context]
engine = "mkdocs"
Use zenzic init to scaffold a config file. If pyproject.toml exists, the command will prompt whether to embed the config there. Use zenzic init --pyproject to skip the prompt.
Core Settings
docs_dir
| Type | Path |
| Default | "docs" |
Path to the documentation root directory, relative to the repository root.
docs_dir = "content"
snippet_min_lines
| Type | int |
| Default | 1 |
Minimum number of lines for a fenced code block to be syntax-checked. Set to 3 or higher to skip trivial one-liner import stubs.
snippet_min_lines = 3
placeholder_max_words
| Type | int |
| Default | 50 |
Pages with fewer words than this threshold are flagged as short-content placeholders.
placeholder_max_words = 100
placeholder_patterns
| Type | list[str] |
| Default | See below |
Case-insensitive strings that flag a page as containing placeholder text. The default list includes both English and Italian patterns:
# Default patterns (shown for reference — override to customise)
placeholder_patterns = [
"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",
# Italian
"da completare", "in costruzione", "in lavorazione",
"da scrivere", "da aggiungere", "bozza", "prossimamente",
]
validate_same_page_anchors
| Type | bool |
| Default | false |
When true, same-page anchor links (#section) are validated against headings present in the source file. Disabled by default because anchor IDs can be generated by HTML attributes, custom plugins, or build-time macros invisible at source-scan time.
validate_same_page_anchors = true
Exclusion Settings
excluded_dirs
| Type | list[str] |
| Default | ["includes", "stylesheets", "overrides", "hooks"] |
Directories inside docs/ to exclude from orphan and snippet checks. User entries are merged with the immutable System Guardrails (SYSTEM_EXCLUDED_DIRS) -- they can never be removed.
excluded_dirs = ["includes", "stylesheets", "overrides", "hooks", "snippets"]
The following directories are excluded unconditionally, regardless of configuration:
.git, .github, .venv, node_modules, .nox, .tox, .pytest_cache, .mypy_cache, .ruff_cache, __pycache__, .docusaurus, .cache, .hypothesis, .temp
These represent the L1 System Guardrails layer. No configuration can override them.
excluded_file_patterns
| Type | list[str] |
| Default | [] |
Filename glob patterns excluded from all checks (orphan detection, placeholder scanning, reference pipeline, and Shield). Uses fnmatch syntax.
# Skip locale-suffixed files and changelogs
excluded_file_patterns = ["*.it.md", "*.fr.md", "CHANGELOG*.md"]
excluded_assets
| Type | list[str] |
| Default | [] |
Asset paths (relative to docs_dir) excluded from the unused-assets check. Entries may be literal paths or glob patterns (fnmatch syntax). Use for files referenced by the build tool or theme templates rather than by Markdown pages.
excluded_assets = [
"img/favicon.ico",
"img/logo.svg",
"img/social/*.png",
"_category_.json",
]
excluded_asset_dirs
| Type | list[str] |
| Default | ["overrides"] |
Directories inside docs/ whose non-Markdown files are excluded from the unused-assets check. Use for theme override directories whose files are consumed by the build tool rather than referenced from Markdown pages.
excluded_asset_dirs = ["overrides", "theme"]
excluded_build_artifacts
| Type | list[str] |
| Default | [] |
Glob patterns (relative to docs_dir) for assets generated at build time. Links to matching paths are not flagged as broken even when the file does not exist on disk at lint time.
excluded_build_artifacts = ["pdf/*.pdf", "assets/bundle.zip"]
excluded_external_urls
| Type | list[str] |
| Default | [] |
External URLs (or URL prefixes) excluded from the broken-link check in --strict mode. A URL is skipped when it starts with any entry in this list.
excluded_external_urls = [
"https://internal.example.com",
"https://github.com/PythonWoods/unreleased-repo",
]
VCS-Aware Exclusion
The Zenzic Way: Conscious Control vs. Blind Automation
Zenzic deliberately defaults to Conscious Control rather than Blind Automation. Understanding this principle is the key to configuring the tool effectively in production projects.
respect_vcs_ignore is false by default. This is not an oversight — it is a deliberate architectural choice rooted in a concrete failure mode.
The Noisy .gitignore Problem
Consider a repository where docs_dir = "." (the repo root is also the docs root). This is common for projects that lint their README.md, CHANGELOG.md, and other root-level Markdown files. A typical Python project .gitignore contains entries like:
*.egg-info/
.coverage
dist/
htmlcov/
*.pyc
.venv/
If respect_vcs_ignore = true, Zenzic would silently exclude any documentation file whose path matches these patterns. A docs/coverage-report.md page, for instance, would vanish from orphan detection without any diagnostic message. The linter would appear healthy while silently skipping entire documentation subtrees.
The Explicit zenzic.toml is Superior
The excluded_dirs and excluded_file_patterns fields in your project config — zenzic.toml or [tool.zenzic] in pyproject.toml — (L3 in the Layered Exclusion hierarchy) are:
- Visible — exclusions are declared in one authoritative file, not scattered across
.gitignore,.dockerignore, and.npmignore - Reviewable — a new contributor running
git diffsees exactly what Zenzic excludes and why - Stable — exclusions do not change when a developer updates
.gitignorefor unrelated tooling reasons
# zenzic.toml (or [tool.zenzic] in pyproject.toml)
# Explicit exclusions are maintainable and auditable
excluded_dirs = ["includes", "stylesheets", "overrides"]
excluded_file_patterns = ["*.it.md", "CHANGELOG*.md"]
# respect_vcs_ignore = false ← default; omit or set explicitly
When to enable respect_vcs_ignore
Enable it for projects with a clean, documentation-focused .gitignore where VCS-excluded paths genuinely map to documentation that should not be linted (e.g. auto-generated API reference in site/). Always audit the exclusion effect using --show-info after enabling.
respect_vcs_ignore
| Type | bool |
| Default | false |
When true, Zenzic reads .gitignore files from the repository root and docs directory and excludes matching files from all checks. Disabled by default — see The Zenzic Way above for the rationale.
Forced inclusions (included_dirs, included_file_patterns) override VCS exclusions, but System Guardrails are always enforced.
respect_vcs_ignore = true
included_dirs
| Type | list[str] |
| Default | [] |
Directory names inside docs/ that are forcefully included even when excluded by VCS ignore patterns or excluded_dirs. Forced inclusions cannot override System Guardrails (.git, .venv, etc.).
included_dirs = ["generated-api"]
included_file_patterns
| Type | list[str] |
| Default | [] |
Filename glob patterns (fnmatch syntax) forcefully included even when excluded by VCS ignore patterns or excluded_file_patterns. Use for build-generated documentation that should be linted despite being in .gitignore.
included_file_patterns = ["api.generated.md"]
Build Context
The [build_context] table tells Zenzic which documentation engine produced the site and how to resolve locale-specific paths.
engine
| Type | str |
| Default | "mkdocs" |
Build engine identifier. Used by the adapter factory to select the correct path-resolution strategy. Built-in adapters: mkdocs, zensical, docusaurus, vanilla.
[build_context]
engine = "mkdocs"
default_locale
| Type | str |
| Default | "en" |
ISO 639-1 code of the default locale. Used by adapters for i18n fallback logic.
[build_context]
default_locale = "en"
locales
| Type | list[str] |
| Default | [] |
Non-default locale directory names. Pages in locale directories receive special handling during orphan detection and anchor resolution.
[build_context]
locales = ["it", "fr", "de"]
base_url
| Type | str |
| Default | "" |
Site base URL (e.g. "/" or "/docs/"). When set, the adapter uses this value instead of attempting static extraction from the build tool's config file. Recommended when the config file uses dynamic patterns that cannot be parsed statically.
[build_context]
base_url = "/docs/"
fallback_to_default
| Type | bool |
| Default | true |
When true, missing locale-tree assets and pages fall back to the default-locale tree. Mirrors the fallback_to_default option in mkdocs-i18n. Set to false to report every missing locale file as an error.
[build_context]
fallback_to_default = false
CI / Exit Behaviour
fail_under
| Type | int |
| Default | 0 |
Minimum quality score (0--100). If the Sentinel Score falls below this value, zenzic score exits with code 1. A value of 0 disables the threshold (observational mode).
fail_under = 80
strict
| Type | bool |
| Default | false |
When true, treat warnings as errors and validate external URLs via network requests. Equivalent to passing --strict on every invocation of check all, score, or diff.
strict = true
exit_zero
| Type | bool |
| Default | false |
When true, zenzic check all always exits with code 0 even when issues are found. Issues are still printed and scored. Useful for observation-only pipelines. Shield violations (exit code 2) and Blood Sentinel events (exit code 3) are never suppressed.
exit_zero = true
Custom Rules
Project-specific lint rules can be declared inline without writing Python. Each entry applies a regex pattern line-by-line to every .md file.
[[custom_rules]]
id = "ZZ-NOINTERNAL"
pattern = "internal\\.corp\\.example\\.com"
message = "Internal hostname must not appear in public docs."
severity = "error"
[[custom_rules]]
id = "ZZ-NODRAFT"
pattern = "(?i)\\bDRAFT\\b"
message = "Remove DRAFT marker before publishing."
severity = "warning"
| Field | Type | Default | Description |
|---|---|---|---|
id | str | (required) | Stable unique identifier (e.g. "ZZ001") |
pattern | str | (required) | Regex applied to each content line |
message | str | (required) | Human-readable explanation shown in findings |
severity | str | "error" | "error", "warning", or "info" |
Plugins
| Type | list[str] |
| Default | [] |
Explicit allow-list of external rule plugins to activate from the zenzic.rules entry-point group. Core rules shipped by Zenzic are always enabled.
plugins = ["zenzic-no-draft", "zenzic-link-policy"]
Use zenzic plugins list to see all discovered rules and their origins.
CLI Flags
Several configuration values can be overridden per-run via CLI flags on zenzic check all:
| Flag | Overrides | Description |
|---|---|---|
--strict / -s | strict | Treat warnings as errors; validate external URLs |
--exit-zero | exit_zero | Always exit 0 (issues still reported) |
--engine ENGINE | build_context.engine | Override the build engine adapter |
--exclude-dir DIR | (additive) | Additional directories to exclude (repeatable) |
--include-dir DIR | (additive) | Force-include directories even if excluded by config (repeatable). Cannot override System Guardrails |
--show-info | (display) | Show info-level findings (e.g. circular links) |
--format json | (display) | Output in JSON format instead of the Sentinel report |
--fail-under N | fail_under | Exit non-zero if score is below threshold (on zenzic score) |
--quiet / -q | (display) | Minimal one-line output for pre-commit hooks |
Override Priority
CLI flags always override both zenzic.toml and pyproject.toml values for a single run. The full priority chain is:
CLI flags > zenzic.toml > pyproject.toml [tool.zenzic] > built-in defaults
Complete Example
# zenzic.toml — full configuration example
docs_dir = "docs"
snippet_min_lines = 3
placeholder_max_words = 100
validate_same_page_anchors = true
# Exclusions
excluded_dirs = ["includes", "stylesheets", "overrides"]
excluded_file_patterns = ["*.it.md", "*.fr.md"]
excluded_assets = ["img/favicon.ico", "img/social/*.png"]
excluded_asset_dirs = ["overrides"]
excluded_build_artifacts = ["pdf/*.pdf"]
excluded_external_urls = ["https://internal.example.com"]
# VCS-aware discovery
respect_vcs_ignore = true
included_dirs = ["generated-api"]
included_file_patterns = ["api.generated.md"]
# Build engine
[build_context]
engine = "mkdocs"
default_locale = "en"
locales = ["it", "fr"]
base_url = "/"
fallback_to_default = true
# CI behaviour
strict = false
fail_under = 80
exit_zero = false
# Custom rules
[[custom_rules]]
id = "ZZ-NOINTERNAL"
pattern = "internal\\.corp\\.example\\.com"
message = "Internal hostname must not appear in public docs."
severity = "error"
# Plugins
plugins = []
TOML Pitfalls
Field Order is Law
In TOML, every key written after a [section] header belongs to that section, not to the root.
Zenzic loads the root with _build_from_data, which filters against ZenzicConfig.model_fields — any key nested inside an unknown section is silently discarded.
Wrong — all root fields after [project] are swallowed:
[project]
name = "My Project"
# ❌ These lines look like root settings but they are INSIDE [project].
# Zenzic ignores them — the section is unknown.
placeholder_patterns = []
docs_dir = "docs"
Correct — all root fields BEFORE the first section header:
# ✔ Root fields first
docs_dir = "docs"
placeholder_patterns = []
fail_under = 100
# ✔ Sub-table section last
[build_context]
engine = "docusaurus"
base_url = "/"
Unknown Sections Emit a Warning
Since v0.6.1, Zenzic emits a WARNING when it encounters an unrecognised TOML section (e.g. [project]) instead of discarding it silently.
If you see:
WARNING zenzic.toml: unknown section [project] will be ignored …
move all settings that follow that header to the top of the file, before any [section] tag.
Dogfooding Pattern with Docusaurus
Documenting a linter with its own linter creates intentional false positives: pages that explain placeholder patterns will trigger the placeholder checker.
Disable the checker in the zenzic.toml of the documentation repository:
# Doc repository — explains lint rules without triggering them
placeholder_patterns = [] # disabled: this doc describes patterns by example
placeholder_max_words = 0 # disabled: glossary entries are intentionally short
[build_context]
engine = "docusaurus"