Metadata-Version: 2.4
Name: doxygen-guard
Version: 1.4.2
Summary: Pre-commit hook that validates doxygen comments for presence, version staleness, and custom tag syntax
Project-URL: Homepage, https://github.com/tvanfossen/doxygen-guard
Project-URL: Source, https://github.com/tvanfossen/doxygen-guard
Project-URL: Issues, https://github.com/tvanfossen/doxygen-guard/issues
Project-URL: Changelog, https://github.com/tvanfossen/doxygen-guard/blob/main/CHANGELOG.md
Author: Tristan VanFossen
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Requires-Dist: pyyaml<8,>=6.0
Requires-Dist: tree-sitter-c<0.23.5,>=0.23
Requires-Dist: tree-sitter-cpp<0.23.5,>=0.23
Requires-Dist: tree-sitter-python<0.23.5,>=0.23
Requires-Dist: tree-sitter<0.24,>=0.23
Description-Content-Type: text/markdown

# 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](#consumer-contract) for the machine-readable surface that downstream tools build on.

## Quick Start

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

```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

```yaml
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

```c
/**
 * @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:

```yaml
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](#consumer-contract).

### Requirements catalog

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

```yaml
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:

```bash
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.

```bash
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:

```yaml
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:

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

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

For Python:

```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):

```python
## @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):

```python
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](#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 `@@`:

```c
/**
 * @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:

```yaml
validate:
  version_gate:
    current_version: "auto:git"
    version_field: "min_version"
```

2. **Add `@return` to non-void functions** — required by default. Disable with `presence.require_return: false` during migration.

3. **Exclude paths you're not ready to cover**:

```yaml
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

```bash
# 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](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](LICENSE).
