Metadata-Version: 2.4
Name: doxygen-guard
Version: 1.3.1
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-java<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++, java, 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 |
|-----|--------|
| `@internal` | Exempt from `@req`; excluded from the coverage report's unmapped list |
| `@utility` | Exempt from `@req` |
| `@callback` | Exempt from `@req` |

### 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++, Java, 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 |
| `presence.skip_forward_declarations` | bool | `true` | Skip C/C++ forward declarations |
| `version.require_present` | bool | `true` | Require `@version` 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 |

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

## 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 equivalent forms. Use whichever
fits your codebase — they are interchangeable on a per-function basis.

**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 |
| Java | `.java` | `/** ... */` | 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/

# 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).
