Skip to main content

doxygen-guard

Pre-commit hook that enforces doxygen documentation and reports the change impact of what you commit. Language- and architecture-agnostic.

Scope is deliberately narrow: enforcement and impact. Diagram generation and symbol indexing are out of scope — see Consumer Contract for the machine-readable surface that downstream tools build on.

Quick Start

1. Add the hook to .pre-commit-config.yaml

repos:
  - repo: https://github.com/tvanfossen/doxygen-guard
    rev: main
    hooks:
      - id: doxygen-guard
        types_or: [c, c++, python]

2. Create .doxygen-guard.yaml in your repo root

output_dir: docs/generated/

validate:
  exclude:
    - "^tests/"
    - "^\\.venv/"
  tags:
    req:
      pattern: "^REQ-[A-Z]+-[0-9]{3}$"
  version_gate:
    current_version: "auto:git"
    version_field: "min_version"

impact:
  requirements:
    file: docs/requirements.yaml
    format: yaml

3. Add doxygen to your functions

/**
 * @brief Read temperature from sensor hardware.
 * @version 1.0
 * @req REQ-SENSE-001
 * @return Raw ADC value
 */
int Sensor_ReadTemperature(void) {
    return hw_read_adc(TEMP_CHANNEL);
}

Run pre-commit run --all-files — violations print to stderr and the impact report appears in <output_dir>/impact/.

What It Does

Validation (pre-commit gate)

Every function in staged files is checked for:

  • Presence — must have @brief, @version, and @return (non-void functions)
  • Version staleness — if function body changed (git diff), @version must be updated
  • Tag syntax — tag values validated against configured patterns
  • Requirement coverage — functions must have @req or an exemption tag

Exemption Tags

Tag Effect
@dg_internal Exempt from @req; excluded from the coverage report's unmapped list
@utility Exempt from @req
@callback Exempt from @req

These are tool vocabulary, not doxygen commands — define them as no-op ALIASES in your Doxyfile if you also generate docs.

Changed in 1.4.0: exemption used to be spelled @internal. Doxygen's \internal hides everything after it in the block when INTERNAL_DOCS is off, so a block written to satisfy this gate silently lost its @return in generated documentation. @internal is still recognised — it no longer grants an exemption. Rename your exemption tags, or add internal to validate.extra_tags and keep using it for its real doxygen purpose.

Change-Impact Reports

Cross-references git diff with parsed functions to show which requirements are affected by staged changes. Reports in markdown and JSON at <output_dir>/impact/.

Requirement Coverage

doxygen-guard coverage cross-references @req tags against the catalog and reports covered, uncovered and orphan requirements, plus documented functions carrying no @req. Exit code 1 when gaps exist.

Configuration Reference

validate section

Key Type Default Description
languages dict C, C++, Python Per-language function patterns and comment styles
presence.require_doxygen bool true Require doxygen on every function
presence.require_return bool true Require @return on non-void functions
version.tag string @version Tag used for the per-function revision counter
version.require_present bool true Require the revision tag
version.require_increment_on_change bool true Require version bump when body changes
exclude list [] Regex patterns for files to skip — see doxygen-guard files
duplicate_tags_error bool true Flag repeated @brief/@version/@return/@file in one block
tags.req.cross_reference bool true Validate @req IDs exist in requirements file
version_gate.current_version string auto:git, auto:cmake, or explicit version
version_gate.version_field string Column in requirements file for version gating

version.tag exists because this tool treats the tag as a per-function revision counter — incremented whenever the body changes — whereas doxygen documents \version as free-form version prose. If your project already uses \version idiomatically, point this tool at a different tag and alias it in your Doxyfile:

validate:
  version:
    tag: "@revision"
  extra_tags: ["revision"]

Repeated @version entries are legal (doxygen accumulates them) and are not reported as duplicates.

impact section

Key Type Default Description
requirements.file string Path to the requirements catalog
requirements.format string yaml Format: yaml, csv, or json
requirements.id_column string Req ID ID column — csv/json only
requirements.name_column string name Requirement name column/field

These defaults are also emitted by doxygen-guard config --schema. Read them from there rather than copying them — see Consumer Contract.

Requirements catalog

The preferred form is a mapping keyed by requirement ID. The ID is the key, so no id_column applies:

requirements:
  REQ-VAL-001:
    name: Doxygen presence check      # required
    subsystem: Validate               # optional
    min_version: v0.1.0               # optional
    description: >-                   # optional
      Every function must have a doxygen comment
    acceptance_criteria: >-           # optional
      Undocumented functions produce presence violations

The catalog is validated on load. A missing file, an unknown format, a wrong document shape, a missing name, or an ID failing validate.tags.req.pattern each raise RequirementsError — the run fails rather than silently proceeding with an empty catalog. Flat csv/json row formats remain supported and use id_column.

Using doxygen Alongside This Tool

doxygen-guard invents tags doxygen does not define (@req and the exemption markers), so doxygen would emit unknown-command warnings on files written to satisfy the gate. Generate the settings that fix that:

doxygen-guard doxyfile > doxygen-guard.doxyfile

Then include it from your Doxyfile:

@INCLUDE = doxygen-guard.doxyfile

It declares each tool-owned tag as an ALIASES entry — @req becomes a cross-referenced "Requirement Index" section, the exemption markers render as nothing — and enables WARN_IF_DOC_ERROR. doxygen is not a dependency of doxygen-guard; this subcommand is pure text generation and the pre-commit hook never invokes doxygen.

The fragment deliberately leaves WARN_IF_UNDOCUMENTED and WARN_NO_PARAMDOC commented out. They are policy rather than validity and are stricter than this tool — against doxygen-guard's own source they raise 399 warnings, 309 of them demanding @param for every parameter, none of which this tool considers defects. Uncomment them if you want doxygen's standard as well.

Division of authority: doxygen decides whether a comment is valid doxygen. doxygen-guard decides whether it satisfies your policy — catalog membership, revision increment against git diff, per-check severity, per-staged-file scoping. Doxygen cannot express any of the latter, and its failure switch is whole-run and coarse.

Consumer Contract

Tools built on doxygen-guard must read the contract from the tool, not re-derive it. Every declaration the gate honours is observable in this output.

doxygen-guard config --schema       # schema, defaults, catalog constants, contract_version
doxygen-guard config --effective    # the merged config in force and what it resolved to
doxygen-guard files src/            # the exact post-exclude file set the gate walks

All three emit JSON carrying a contract_version. Compare it against the value your tool was written for; when it moves, re-check your assumptions.

doxygen-guard files is the authoritative answer to "which files does the gate consider?" — it applies validate.exclude exactly as validation does. A consumer that walks the tree itself and diffs against this output will detect its own divergence instead of silently reporting on files the gate never sees.

Typed errors are importable from doxygen_guard.errors: GuardError with ConfigError and RequirementsError subclasses. load_config raises rather than exiting, so in-process callers can handle failures; exit codes are produced only at the CLI boundary.

Passthrough config

Consumers may declare their own sections in .doxygen-guard.yaml using an x- prefix, at any nesting level. The guard validates that they parse but never interprets them:

x-my-tool:
  index_path: .cache/index

Any other unknown key is an error.

File-Level Doxygen

Each source file should have a file-level doxygen block:

/**
 * @file
 * @brief Sensor hardware abstraction layer.
 * @version 1.0
 */

Enable validate.presence.require_file_doxygen: true to enforce file-level blocks.

For Python:

## @file
## @brief Configuration loading and validation.
## @version 1.0

Python Function Docstring Style

Python functions accept doxygen tags in two forms. Both satisfy this tool on a per-function basis.

They are not equivalent to doxygen itself. Doxygen renders """ docstrings as preformatted text and its special commands do not work inside them by default. If you also generate docs, either use the two-hash style below, open the docstring with """!, or set PYTHON_DOCSTRING = NO in your Doxyfile. Earlier releases described the two styles as interchangeable, which was true of the gate and false of doxygen.

Two-hash block above the def (classic doxygen-for-Python convention):

## @brief Apply a unified-diff patch to a project directory.
#  @version 1.0
#  @req REQ-PATCH-001
#  @return 0 on success, non-zero on failure.
def apply_patch(repo_path: str, patch: str) -> int:
    ...

Inside the PEP 257 docstring (idiomatic Python — Sphinx/IDE-friendly):

def apply_patch(repo_path: str, patch: str) -> int:
    """Apply a unified-diff patch via `git apply`.

    @brief Apply a unified-diff patch to a project directory.
    @version 1.0
    @req REQ-PATCH-001
    @return 0 on success, non-zero on failure.
    """
    ...

When both styles are present on the same function, the ##-block above takes precedence. A docstring without any recognized tag is not treated as a doxygen block (so prose-only docstrings remain free-form).

Config Validation

The config file is validated at load time against a built-in schema. Unknown keys are rejected, with a suggestion when one is close:

doxygen-guard: Invalid config in .doxygen-guard.yaml
Unknown config key: validate.exclud — did you mean 'exclude'?

Keys prefixed x- are exempt (see Passthrough config). The schema itself is available via doxygen-guard config --schema.

Escaping @ in Documentation Text

As in doxygen itself, @word is a command anywhere in a block — including mid-sentence. To mention a tag name in prose, escape it as \@ or @@:

/**
 * @brief Rows carry their declared \@req IDs.
 * @version 1.0
 */

Without the escape, @req IDs parses as a req tag valued IDs, and the gate reports a requirement that does not exist.

Adopting on an Existing Codebase

For repos with existing code that has no doxygen, adopt incrementally:

  1. Start with validation only — add @brief and @version to functions as you touch them. Use version_gate to only enforce @req on functions added after a specific version:
validate:
  version_gate:
    current_version: "auto:git"
    version_field: "min_version"
  1. Add @return to non-void functions — required by default. Disable with presence.require_return: false during migration.

  2. Exclude paths you're not ready to cover:

validate:
  exclude:
    - "^vendor/"
    - "^legacy/"

Supported Languages

Language Extensions Comment Style Body Detection
C .c, .h /** ... */ Brace matching
C++ .cpp, .hpp, .cc, .cxx /** ... */ Brace matching
Python .py ## ... block above def or @tag lines inside the docstring Indentation

C++ template functions (template<typename T> void func(...)) are fully supported — doxygen comments are associated via tree-sitter AST sibling detection, handling template_declaration wrappers correctly.

CLI Usage

# Pre-commit mode (default — called by pre-commit)
doxygen-guard [--config path] [files...]

# Explicit subcommands
doxygen-guard validate --no-git src/*.c
doxygen-guard impact --staged src/*.c
doxygen-guard coverage src/

# Consumer contract (JSON)
doxygen-guard config --schema
doxygen-guard config --effective
doxygen-guard files src/
doxygen-guard doxyfile

# Verbose mode — logs which config sections were declared vs defaulted
doxygen-guard -v coverage src/

Scope and Direction

The tool is feature-complete for what it does: enforcement and change impact. See ROADMAP.md for what is being considered, what is explicitly out of scope, and the three tests any new feature has to pass.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

doxygen_guard-1.4.1.tar.gz (125.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

doxygen_guard-1.4.1-py3-none-any.whl (51.2 kB view details)

Uploaded Python 3

File details

Details for the file doxygen_guard-1.4.1.tar.gz.

File metadata

  • Download URL: doxygen_guard-1.4.1.tar.gz
  • Upload date:
  • Size: 125.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for doxygen_guard-1.4.1.tar.gz
Algorithm Hash digest
SHA256 c75723c3012705031cb722b0407e176261152f0b83257191558584e66997d21b
MD5 408e3798913f1bc2035bb7b506378fb1
BLAKE2b-256 e790823b1d65879c163c4c78a9dac89fe4675c234170bb6c726aecae7c8582a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for doxygen_guard-1.4.1.tar.gz:

Publisher: release.yml on tvanfossen/doxygen-guard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file doxygen_guard-1.4.1-py3-none-any.whl.

File metadata

  • Download URL: doxygen_guard-1.4.1-py3-none-any.whl
  • Upload date:
  • Size: 51.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for doxygen_guard-1.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c2f9d3c85caec076f3635d7aaee287a9ac5ce70604998c7cae3ccc2b4fd982b6
MD5 9323e7fc3a86a19fc33e2e2f32ec3e01
BLAKE2b-256 bad895e037101847376bfbdbbec2474cee659fa1c826e3cb049d0c5525a9d5df

See more details on using hashes here.

Provenance

The following attestation bundles were made for doxygen_guard-1.4.1-py3-none-any.whl:

Publisher: release.yml on tvanfossen/doxygen-guard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.4.2

2 files

This release

1.4.1 This release

2 files

1.4.0

2 files

1.3.1

2 files

1.2.9

2 files

1.2.8

2 files

1.2.6

2 files

1.2.5

2 files

1.2.4

2 files

1.2.0

2 files

1.1.16

2 files

1.1.14

2 files

1.1.12

2 files

1.1.11

2 files

1.1.10

2 files

1.1.9

2 files

1.1.8

2 files

1.1.7

2 files

1.1.6

2 files

1.1.5

2 files

1.1.4

2 files

1.1.3

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page