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.
TOML syntax dictates that any key-value pair defined after a section header (e.g., [build_context]) belongs to that section. Top-level keys like docs_dir, placeholder_patterns, or excluded_dirs MUST be placed at the very top of .zenzic.toml, before any [section] is declared. Otherwise, they will be silently ignored by the parser.
Config File Priority
Zenzic resolves configuration using a 4-level hierarchy — the most specific source wins:
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | CLI flags | --engine, --exclude-dir, --strict, etc. Override every other source for the current run. |
| 2 | .zenzic.toml | Standalone file at the repository root — the authoritative sovereign config |
| 3 | pyproject.toml | [tool.zenzic] table inside pyproject.toml |
| 4 (lowest) | Built-in defaults | Hardcoded defaults when no config file is found |
CLI flags always win. A flag like --engine mkdocs overrides the engine value in .zenzic.toml for that single run without modifying any file.
Exclusions and inclusions are cumulative, not replacing:
--exclude-diradds to the list already defined in the config file.--include-diris a force override: a directory excluded in.zenzic.tomlbut included via--include-dirwill be scanned. The only exception is Level 1 System Guardrails (node_modules,.git, etc.) — these cannot be force-included.
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
docs_dir = "docs"
snippet_min_lines = 3
strict = true
[build_context]
engine = "mkdocs"
Embedded in 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.
zenzic init also scaffolds .zenzic.local.toml as a machine-local overlay. This
file is designed for Local Sovereignty: local values override shared config, but
must remain private on your workstation.
.zenzic.local.toml Local Sanctuary
.zenzic.local.toml is the private maneuvering space for engineers.
- It is loaded after shared config (
.zenzic.tomlor[tool.zenzic]) and therefore wins locally. - It is intended for machine-specific paths, temporary cleanup knobs, diagnostics, and private secrets.
- It is never a team policy file.
When zenzic init runs in a Git repository, it enforces .zenzic.local.toml inside
.gitignore (creating or updating .gitignore safely, without destructive edits).
# --- ZENZIC LOCAL OVERRIDES ---
# This file is auto-generated and must stay in .gitignore.
# Everything declared here overrides shared .zenzic.toml only on your machine.
[core]
# docs_dir = "my/custom/path/to/docs"
forbidden_patterns = []
[governance]
# suppression_cap = 100
# suppression_cap_fail_hard = false
[secrets]
# github_pat = "YOUR_GITHUB_PAT"
[debug]
# log_level = "DEBUG"
[env]
# ZENZIC_FORCE_COLOR = "true"
Use .zenzic.toml for shared constitutional governance. Use .zenzic.local.toml
for local experiments and private data only.
Source-of-Truth Introspection (zenzic config explain)
Use zenzic config explain to verify both active value and origin for each
config field.
zenzic config explain
Expected provenance semantics:
local->.zenzic.local.toml (Override)global->.zenzic.tomldefault-> built-in fallback
Example (governance override):
suppression_cap = 45 Source: .zenzic.local.toml (Override)
Governance Suppression Contract (Suppression CAP)
[governance]
suppression_cap = 30
suppression_cap_fail_hard = true
per_file_ignores = { "docs/legacy/*.md" = ["Z601"] }
suppression_capandsuppression_cap_fail_hardenforce CAP governance.per_file_ignoresdefines scoped suppressions in normal runs.zenzic check all --auditignores both inline suppressions andper_file_ignoresto expose full debt truth.
# Create a .zenzic.toml file at the project root
zenzic init
# Or embed config in pyproject.toml
zenzic init --pyproject
Core Settings
docs_dir
| Type | Path |
| Default | "docs" |
Path to the documentation root directory, relative to the repository root.
When omitted, Zenzic defaults to "docs". Set to "." to scan the entire
repository root (L1 system exclusions still apply). Set to any other
relative path when your project stores documentation in a non-standard
location such as website/ or content/.
# docs_dir = "docs" # default — omit if your docs live in docs/
docs_dir = "." # scan the entire repository (e.g. README-only projects)
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 | true |
When true, same-page anchor links (#section) are validated against headings present in the source file. Enabled by default for stronger source-level integrity checks. Disable it only when anchor IDs are 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"] |
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", "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 credential scanner). 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",
]
Never add an entire domain as an exclusion (e.g. "https://zenzic.dev/"). A blanket domain exclusion creates a permanent blindspot that survives content restructures and silently masks broken links. Entries must target specific URLs or prefixes, not root domains. Use --exclude-url <url> at the CLI for temporary, one-off skips.
VCS-Aware Exclusion
See Exclusion Design for the rationale behind conscious exclusion vs. blind VCS automation.
respect_vcs_ignore
| Type | bool |
| Default | true |
When true, Zenzic reads .gitignore files from the repository root and docs directory and excludes matching files from all checks. Enabled by default — see Exclusion Design for operational guidance.
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 | "auto" |
Build engine identifier. Used by the adapter factory to select the correct path-resolution strategy. Built-in adapters: mkdocs, zensical, docusaurus, standalone.
When set to "auto" (the default), Zenzic probes the project root at runtime using engine auto-discovery, scanning for engine config files in priority order:
zensical.toml→zensicaldocusaurus.config.ts/docusaurus.config.js→docusaurusmkdocs.yml→mkdocs- (no match) →
standalone
For production CI, pin the engine explicitly to skip discovery overhead:
[build_context]
engine = "docusaurus"
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
I18N Parity Settings
The [i18n] section controls structural translation parity checks (Z907) and
frontmatter key parity across language mirrors.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Activates the i18n parity scanner |
base_lang | str | "en" | Base language code |
base_source | Path | "docs" | Base-language source root |
targets | dict[str, Path] | {} | Mapping of locale -> mirror root |
strict_parity | bool | true | Missing mirror is error when true, warning when false |
require_frontmatter_parity | list[str] | ["title", "description"] | Required frontmatter keys in translated pages |
extra_sources | list[I18nSource] | [] | Additional base/targets pairs (e.g. developers plugin docs) |
[i18n]
enabled = true
base_lang = "en"
base_source = "docs"
strict_parity = true
require_frontmatter_parity = ["title", "description"]
[i18n.targets]
it = "i18n/it/docusaurus-plugin-content-docs/current"
[[i18n.extra_sources]]
base_source = "developers"
[i18n.extra_sources.targets]
it = "i18n/it/docusaurus-plugin-content-docs-developers/current"
CI / Exit Behaviour
fail_under
| Type | int |
| Default | 0 |
Minimum quality score (0--100). If the Zenzic Score falls below this value, zenzic score exits with code 1. A value of 0 disables the threshold (observational mode).
fail_under = 80
See Exclusion Design — Governance Score Math for the flat-cost model and hybrid governance policy design.
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. Credential scanner violations (exit code 2) and path traversal guard events (exit code 3) are never suppressed.
exit_zero = true
Project Metadata
release_name
| Type | str |
| Default | "" |
| Section | [project_metadata] |
The current release codename. Used as the protected term in brand_obsolescence enforcement — Zenzic emits Z601 if this string appears as an obsolete term in documentation.
[project_metadata]
release_name = "Graphite"
badge_stamp_files
| Type | list[str] |
| Default | ["README.md"] |
| Section | [project_metadata] |
Files updated by zenzic score --stamp. Each file must contain one or both HTML comment markers: <!-- zenzic:audit-badge --> and <!-- zenzic:score-badge -->. The Shields.io badge URL on the line immediately following each marker is replaced in place with deterministic audit and score telemetry.
The stamp runs before exit-code checks, so the badge always reflects the actual score — including a red badge in local development, which is immediate feedback that the commit will be rejected by CI.
[project_metadata]
badge_stamp_files = ["README.md", "README.it.md"]
Add one or both markers to each listed file, followed on the next line by any Shields.io badge as a placeholder. See Official Badges for the complete setup guide.
Governance Settings
brand_obsolescence
| Type | list[str] |
| Default | [] |
| Section | [governance] |
| Finding | Z601 BRAND_OBSOLESCENCE |
A governance rule to enforce terminology standards across documentation. Ideal for corporate rebranding or deprecating internal project names. Zenzic ships with an empty default list — teams configure their own deprecated term lists here.
When a term in this list appears in any scanned file, Zenzic emits Z601 BRAND_OBSOLESCENCE with exit code 2 (same severity as a credential leak). Historical files (e.g. CHANGELOG*.md) are excluded via excluded_file_patterns. Use an inline [HISTORICAL] comment to suppress individual intentional references in other files.
[governance]
brand_obsolescence = [
"OldProductName",
"LegacyBrand",
"DeprecatedInternalTerm",
]
Pattern matching: case-sensitive whole-word scan. The term "Deprecated" does not match "DeprecatedFeature" or "deprecated".
Scope: applies to all files within the active docs_dir scan scope, subject to the standard exclusion hierarchy.
i18n_parity
| Type | bool |
| Default | false |
| Section | [governance] |
| Finding | Z602 I18N_PARITY |
Enables governance reporting for translation parity on configured locale trees.
per_file_ignores
| Type | dict[str, list[str]] |
| Default | {} |
| Section | [governance] |
Scoped suppressions per glob pattern. Security findings remain non-suppressible.
directory_policies
| Type | dict[str, list[str]] |
| Default | {} |
| Section | [governance] |
Strategic directory-level policy exemptions (zero debt). In --audit mode,
these findings are surfaced with the [POLICY_EXEMPTION] label.
suppression_cap_scope
| Type | "all" |
| Default | "all" |
| Section | [governance] |
Defines suppression counting scope. Current supported value is "all".
suppression_cap_fail_hard
| Type | bool |
| Default | true |
| Section | [governance] |
When true, exceeding suppression_cap triggers immediate exit code 1.
[governance]
i18n_parity = true
suppression_cap = 30
suppression_cap_scope = "all"
suppression_cap_fail_hard = true
[governance.per_file_ignores]
"docs/legacy/**" = ["Z601"]
[governance.directory_policies]
"blog/**" = ["Z601"]
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 inspect capabilities 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 Zenzic 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
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
Zenzic, 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"