# GitVersioned Full Documentation

This file contains the concatenated core configurations, codebase, and documentation for the project to provide comprehensive context to LLMs.

# SECTION 1: PROJECT CONFIGURATIONS

## File: pyproject.toml

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "gitversioned"
dynamic = ["version"]
description = "Opinionated PEP 440 Python versioning for Git repos and submodules. Enforces CI/User authority and generates rich version.py files with deep metadata for auditability. Native Hatch & Setuptools support. Simple, predictable, and foolproof automation."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "Apache-2.0" }
authors = [{ name = "Mark Kurtz" }]
classifiers = [
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
]
dependencies = [
  "loguru~=0.7",
  "packaging>=26.0",
  "pydantic~=2.0",
  "pydantic-settings~=2.0",
  "setuptools>=64.0.0",
  "tomli~=2.0; python_version < '3.11'",
  "tstr>=0.4.1",
  "typer>=0.12",
]

[project.optional-dependencies]
hatch = ["hatchling~=1.18"]
maturin = ["maturin>=1.0,<2.0"]
opentelemetry = ["opentelemetry-api>=1.0.0", "opentelemetry-sdk>=1.0.0"]

[dependency-groups]
test = [
  "pytest>=9.0,<10",
  "pytest-asyncio>=1.3,<2",
  "pytest-cov>=7.1,<8",
  "pytest-mock>=3.15,<4",
  "respx>=0.23,<1",
  "build",
  "wheel",
  "maturin>=1.0,<2.0",
]
lint = [
  "ty",
  "ruff>=0.15,<1",
  "mdformat>=1.0,<2",
  "mdformat-frontmatter>=2.0,<3",
  "mdformat-gfm>=1.0,<2",
  "mdformat-footnote>=0.1.1,<1",
  "mdformat-gfm-alerts>=1.0.1,<3",
  "yamllint>=1.35,<2",
  "urlchecker>=0.0.35",
  "phmdoctest>=1.4,<2",
  "yamlfix>=1.19,<2",
  "taplo",
]
docs = [
  "zensical>=0.0.40",
  "mkdocs-gen-files>=0.5.0",
  "mkdocstrings>=0.24.0",
  "mkdocstrings-python>=1.9.0",
  # Temporary bridge: Zensical compatible fork of mike.
  # Update to Zensical native versioning once stabilized.
  "mike @ git+https://github.com/squidfunk/mike.git",
]
security = [
  "detect-secrets>=1.5,<2",
  "checkov>=3.0,<4",
  "semgrep>=1.73,<2",
  "pip-audit>=2.7,<3",
]
environment = ["hatch>=1.12.0"]
dev = [
  { include-group = "test" },
  { include-group = "lint" },
  { include-group = "docs" },
  { include-group = "security" },
  { include-group = "environment" },
]

[project.scripts]
gitversioned = "gitversioned.__main__:main"

[project.urls]
Homepage = "https://github.com/markurtz/git-versioned"
Repository = "https://github.com/markurtz/git-versioned.git"
Issues = "https://github.com/markurtz/git-versioned/issues"
Documentation = "https://markurtz.github.io/git-versioned/"

[project.entry-points.hatch]
gitversioned = "gitversioned.plugins.hatchling_plugin"

[project.entry-points."setuptools.finalize_distribution_options"]
gitversioned = "gitversioned.plugins.setuptools_plugin:finalize_distribution_options"

[tool.hatch.version]
path = "src/gitversioned/__init__.py"
pattern = "(?m)^__version__[^=]*= *(['\"])v?(?P<version>.+?)\\1"

# ==============================================================================
# Hatch Environment Configurations
# ==============================================================================

[tool.hatch.envs.default]
dependency-groups = ["dev"]

[tool.hatch.envs.default.env-vars]
PYTHON_TARGETS = "docs examples scripts src tests"
OCI_IMAGE = "gitversioned:latest"
MDFORMAT_TARGETS = ".devcontainer/ .github/ docs/*.md docs/community/ docs/examples/ docs/getting-started/ docs/guides/ examples/ scripts/ src/ tests/ *.md"
YAML_TARGETS = ".devcontainer/ .github/ docs/ examples/ scripts/ src/ tests/ *.yml *.yaml"
TOML_TARGETS = ".devcontainer/ .github/ docs/ examples/ scripts/ src/ tests/ *.toml"
PYTHON_SRC = "src"
PYTHON_UNIT_TESTS = "tests/python/unit"
PYTHON_INT_TESTS = "tests/python/integration"
PYTHON_COV_DIR = "coverage/python"
E2E_TESTS = "tests/e2e"
DOC_TESTS_PATH = ".tests/docs"
SECRETS_BASELINE = ".detect-secrets.scan.json"
RUN_OCI_SCRIPT = "scripts/run_oci.py"
CHECK_LINKS_SCRIPT = "scripts/check_links.py"
GENERATE_DOC_TESTS_SCRIPT = "scripts/generate_doc_tests.py"

[tool.hatch.envs.all]
template = "default"
builder = true

[tool.hatch.envs.all.scripts]
lint = [
  "hatch run python:lint {args}",
  "hatch run oci:lint {args}",
  "hatch run project:lint {args}",
]
format = [
  "hatch run python:format {args}",
  "hatch run oci:format {args}",
  "hatch run project:format {args}",
]
types = [
  "hatch run python:types {args}",
  "hatch run oci:types {args}",
  "hatch run project:types {args}",
]
security = [
  "hatch run python:security {args}",
  "hatch run oci:security {args}",
  "hatch run project:security {args}",
]
quality = [
  "hatch run python:quality {args}",
  "hatch run oci:quality {args}",
  "hatch run project:quality {args}",
]
build = [
  "hatch build {args}",
  "hatch run oci:build {args}",
  "hatch run project:docs {args}",
]
tests = ["hatch run tests-func {args}", "hatch run tests-e2e {args}"]
tests-cov = [
  "hatch run tests-func-cov {args}",
  "hatch run tests-e2e-cov {args}",
]
tests-func = [
  "hatch run python:tests-func {args}",
  "hatch run oci:tests-func {args}",
  "hatch run project:tests-func {args}",
]
tests-func-cov = [
  "hatch run python:tests-func-cov {args}",
  "hatch run oci:tests-func-cov {args}",
  "hatch run project:tests-func-cov {args}",
]
tests-unit = [
  "hatch run python:tests-unit {args}",
  "hatch run oci:tests-unit {args}",
  "hatch run project:tests-unit {args}",
]
tests-unit-cov = [
  "hatch run python:tests-unit-cov {args}",
  "hatch run oci:tests-unit-cov {args}",
  "hatch run project:tests-unit-cov {args}",
]
tests-int = [
  "hatch run python:tests-int {args}",
  "hatch run oci:tests-int {args}",
  "hatch run project:tests-int {args}",
]
tests-int-cov = [
  "hatch run python:tests-int-cov {args}",
  "hatch run oci:tests-int-cov {args}",
  "hatch run project:tests-int-cov {args}",
]
tests-e2e = [
  "hatch run python:tests-e2e {args}",
  "hatch run oci:tests-e2e {args}",
  "hatch run project:tests-e2e {args}",
]
tests-e2e-cov = [
  "hatch run python:tests-e2e-cov {args}",
  "hatch run oci:tests-e2e-cov {args}",
  "hatch run project:tests-e2e-cov {args}",
]
docs = [
  "hatch run python:docs {args}",
  "hatch run oci:docs {args}",
  "hatch run project:docs {args}",
]
docs-serve = ["hatch run docs {args}", "hatch run project:docs-serve {args}"]

# ==============================================================================
# Python Environment
# ==============================================================================
[tool.hatch.envs.python]
template = "default"
builder = true

[tool.hatch.envs.python.scripts]
lint = [
  "ruff check {args:{env:PYTHON_TARGETS}}",
  "ruff format --check {args:{env:PYTHON_TARGETS}}",
]
format = [
  "ruff check --fix {args:{env:PYTHON_TARGETS}}",
  "ruff format {args:{env:PYTHON_TARGETS}}",
]
types = "ty check {args:{env:PYTHON_TARGETS}}"
security = [
  "semgrep scan --error {args}",
  "pip-audit {args}",
  "ruff check --select S {args:{env:PYTHON_TARGETS}}",
]
quality = [
  "hatch run python:format {args}",
  "hatch run python:lint {args}",
  "hatch run python:types {args}",
  "hatch run python:security {args}",
]
tests-func = "pytest {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}"
tests-func-cov = [
  "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"",
  "pytest --cov={env:PYTHON_SRC} --cov-context=test --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-func.md {args:{env:PYTHON_UNIT_TESTS} {env:PYTHON_INT_TESTS}}",
  "coverage report --contexts=\".*tests/python/unit/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-unit.md",
  "coverage report --contexts=\".*tests/python/integration/.*|^$\" --format=markdown > {env:PYTHON_COV_DIR}/coverage_tests-int.md",
  "python -c \"import pathlib; [p.write_text('\\n'.join(line for line in p.read_text().splitlines() if not line.startswith('Combined'))) for p in (pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-unit.md'), pathlib.Path('{env:PYTHON_COV_DIR}/coverage_tests-int.md')) if p.exists()]\"",
]
tests-unit = "pytest {args:{env:PYTHON_UNIT_TESTS}}"
tests-unit-cov = [
  "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"",
  "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-unit.md {args:{env:PYTHON_UNIT_TESTS}}",
]
tests-int = "pytest {args:{env:PYTHON_INT_TESTS}}"
tests-int-cov = [
  "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"",
  "pytest --cov={env:PYTHON_SRC} --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-int.md {args:{env:PYTHON_INT_TESTS}}",
]
tests-e2e = [
  "hatch build",
  "pip install --force-reinstall --no-deps --no-index --find-links=dist gitversioned",
  "pytest {args:{env:E2E_TESTS}}",
]
tests-e2e-cov = [
  "python -c \"import pathlib; pathlib.Path('{env:PYTHON_COV_DIR}').mkdir(parents=True, exist_ok=True)\"",
  "hatch build",
  "pip install --force-reinstall --no-deps --no-index --find-links=dist gitversioned",
  "pytest --cov=gitversioned --cov-report=term --cov-report=markdown:{env:PYTHON_COV_DIR}/coverage_tests-e2e.md {args:{env:E2E_TESTS}}",
]
docs = [
  "python -c \"import pathlib; pathlib.Path('.docs').mkdir(exist_ok=True)\"",
  "typer src/gitversioned/__main__.py utils docs --name gitversioned --output .docs/cli.md",
]

# ==============================================================================
# OCI Environment
# ==============================================================================
[tool.hatch.envs.oci]
template = "default"

[tool.hatch.envs.oci.scripts]
lint = [
  "python {env:RUN_OCI_SCRIPT} hadolint {args}",
  "python {env:RUN_OCI_SCRIPT} compose-config {args}",
  "python {env:RUN_OCI_SCRIPT} dclint {args}",
]
format = "python {env:RUN_OCI_SCRIPT} dclint --fix {args}"
types = "echo '[INFO] Type checking is not applicable for the OCI environment' {args}"
security = [
  "python {env:RUN_OCI_SCRIPT} dockle {args}",
  "python {env:RUN_OCI_SCRIPT} trivy {args}",
]
quality = [
  "hatch run oci:format {args}",
  "hatch run oci:lint {args}",
  "hatch run oci:types {args}",
  "hatch run oci:security {args}",
]
tests-func = [
  "hatch run oci:tests-unit {args}",
  "hatch run oci:tests-int {args}",
]
tests-func-cov = [
  "hatch run oci:tests-unit-cov {args}",
  "hatch run oci:tests-int-cov {args}",
]
tests-unit = "echo '[INFO] No unit tests are defined for the OCI environment' {args}"
tests-unit-cov = [
  "hatch run oci:tests-unit {args}",
  "echo '[INFO] No coverage analysis is applicable for unit tests in the OCI environment'",
]
tests-int = "echo '[INFO] No integration tests are defined for the OCI environment' {args}"
tests-int-cov = [
  "hatch run oci:tests-int {args}",
  "echo '[INFO] No coverage analysis is applicable for integration tests in the OCI environment'",
]
tests-e2e = "python {env:RUN_OCI_SCRIPT} cstest {args}"
tests-e2e-cov = [
  "hatch run oci:tests-e2e {args}",
  "echo '[INFO] No coverage analysis is applicable for E2E tests in the OCI environment'",
]
docs = "echo '[INFO] No documentation is defined for the OCI environment' {args}"
build = "docker build -t {env:OCI_IMAGE} . {args}"

# ==============================================================================
# Project Environment
# ==============================================================================
[tool.hatch.envs.project]
template = "default"

[tool.hatch.envs.project.scripts]
lint = [
  "mdformat --check {args:{env:MDFORMAT_TARGETS}}",
  "yamlfix --check {args:{env:YAML_TARGETS}}",
  "yamllint {args:{env:YAML_TARGETS}}",
  "taplo check {args:{env:TOML_TARGETS}}",
]
format = [
  "mdformat {args:{env:MDFORMAT_TARGETS}}",
  "yamlfix {args:{env:YAML_TARGETS}}",
  "taplo fmt {args:{env:TOML_TARGETS}}",
]
types = "echo '[INFO] Type checking is not applicable for the project environment' {args}"
security = [
  "detect-secrets scan --baseline {env:SECRETS_BASELINE} {args:.}",
  "python -m checkov.main --quiet {args:-d .}",
]
quality = [
  "hatch run project:format {args}",
  "hatch run project:lint {args}",
  "hatch run project:types {args}",
  "hatch run project:security {args}",
]
security-update = ["detect-secrets scan {args:.} > {env:SECRETS_BASELINE}"]
tests-func = [
  "hatch run project:tests-unit {args}",
  "hatch run project:tests-int {args}",
]
tests-func-cov = [
  "hatch run project:tests-unit-cov {args}",
  "hatch run project:tests-int-cov {args}",
]
tests-unit = "echo '[INFO] No unit tests are defined for the project environment'"
tests-unit-cov = [
  "hatch run project:tests-unit {args}",
  "echo '[INFO] No coverage analysis is applicable for unit tests in the project environment'",
]
tests-int = [
  "python {env:GENERATE_DOC_TESTS_SCRIPT} {args:{env:PYTHON_TARGETS}}",
  "hatch -e python run pytest {env:DOC_TESTS_PATH} {args}",
]
tests-int-cov = [
  "hatch run project:tests-int {args}",
  "echo '[INFO] No coverage analysis is applicable for integration tests in the project environment'",
]
tests-e2e = "python {env:CHECK_LINKS_SCRIPT} {args:{env:MDFORMAT_TARGETS}}"
tests-e2e-cov = [
  "hatch run project:tests-e2e {args}",
  "echo '[INFO] No coverage analysis is applicable for E2E tests in the project environment'",
]
docs = [
  "python docs/scripts/gen_ref_pages.py generate",
  "python -m zensical {args:build}",
  "python docs/scripts/gen_ref_pages.py clean",
]
docs-serve = [
  "python docs/scripts/gen_ref_pages.py generate",
  "python -m zensical serve {args}",
  "python docs/scripts/gen_ref_pages.py clean",
]


[tool.ty.src]
include = ["src/gitversioned", "tests"]

[tool.ruff]
line-length = 88
indent-width = 4
exclude = ["build", "dist", "env", ".venv", ".tests"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.ruff.lint]
ignore = [
  "COM812",  # ignore trailing comma errors due to older Python versions
  "ISC001",  # implicit string concatenation (often disabled when using ruff format)
  "PD011",   # ignore .values usage since ruff assumes it's a Pandas DataFrame
  "PLR0913", # ignore too many arguments in function definitions
  "PLW1514", # allow Path.open without encoding
  "RET505",  # allow `else` blocks
  "RET506",  # allow `else` blocks
  "S311",    # allow standard pseudo-random generators
  "TC001",   # ignore imports used only for type checking
  "TC002",   # ignore imports used only for type checking
  "TC003",   # ignore imports used only for type checking
]
select = [
  # Rules reference: https://docs.astral.sh/ruff/rules/

  # Code Style / Formatting
  "E",      # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length
  "W",      # pycodestyle: checks adherence to PEP 8 conventions including spacing, indentation, and line length
  "A",      # flake8-builtins: prevents shadowing of Python built-in names
  "C",      # Convention: ensures code adheres to specific style and formatting conventions
  "COM",    # flake8-commas: enforces the correct use of trailing commas
  "ERA",    # eradicate: detects commented-out code that should be removed
  "I",      # isort: ensures imports are sorted in a consistent manner
  "ICN",    # flake8-import-conventions: enforces import conventions for better readability
  "N",      # pep8-naming: enforces PEP 8 naming conventions for classes, functions, and variables
  "NPY",    # NumPy: enforces best practices for using the NumPy library
  "PD",     # pandas-vet: enforces best practices for using the pandas library
  "PT",     # flake8-pytest-style: enforces best practices and style conventions for pytest tests
  "PTH",    # flake8-use-pathlib: encourages the use of pathlib over os.path for file system operations
  "Q",      # flake8-quotes: enforces consistent use of single or double quotes
  "TCH",    # flake8-type-checking: enforces type checking practices and standards
  "TID",    # flake8-tidy-imports: enforces tidy and well-organized imports
  "RUF022", # flake8-ruff: enforce sorting of __all__ in modules

  # Code Structure / Complexity
  "C4",  # flake8-comprehensions: improves readability and performance of list, set, and dict comprehensions
  "C90", # mccabe: checks for overly complex code using cyclomatic complexity
  "ISC", # flake8-implicit-str-concat: prevents implicit string concatenation
  "PIE", # flake8-pie: identifies and corrects common code inefficiencies and mistakes
  "R",   # Refactor: suggests improvements to code structure and readability
  "SIM", # flake8-simplify: simplifies complex expressions and improves code readability

  # Code Security / Bug Prevention
  "ARG",   # flake8-unused-arguments: detects unused function and method arguments
  "ASYNC", # flake8-async: identifies incorrect or inefficient usage patterns in asynchronous code
  "B",     # flake8-bugbear: detects common programming mistakes and potential bugs
  "BLE",   # flake8-blind-except: prevents blind exceptions that catch all exceptions without handling
  "F",     # Pyflakes: detects unused imports, shadowed imports, undefined variables, and various formatting errors in string operations
  "INP",   # flake8-no-pep420: prevents implicit namespace packages by requiring __init__.py
  "PGH",   # pygrep-hooks: detects deprecated and dangerous code patterns
  "PL",    # Pylint: comprehensive source code analyzer for enforcing coding standards and detecting errors
  "RSE",   # flake8-raise: ensures exceptions are raised correctly
  "S",     # flake8-bandit: detects security issues and vulnerabilities in the code
  "SLF",   # flake8-self: prevents incorrect usage of the self argument in class methods
  "T10",   # flake8-debugger: detects the presence of debugging tools such as pdb
  "T20",   # flake8-print: detects print statements left in the code
  "UP",    # pyupgrade: automatically upgrades syntax for newer versions of Python
  "YTT",   # flake8-2020: identifies code that will break with future Python releases

  # Code Documentation
  "FIX", # flake8-fixme: detects FIXMEs and other temporary comments that should be resolved
]

[tool.ruff.lint.extend-per-file-ignores]
"tests/**/*.py" = [
  "S101",    # asserts allowed in tests
  "ARG",     # Unused function args allowed in tests
  "PLR2004", # Magic value used in comparison
  "TCH002",  # No import only type checking in tests
  "SLF001",  # enable private member access in tests
  "S105",    # allow hardcoded passwords in tests
  "S106",    # allow hardcoded passwords in tests
  "S311",    # allow standard pseudo-random generators in tests
  "PT011",   # allow generic exceptions in tests
  "N806",    # allow uppercase variable names in tests
  "PGH003",  # allow general ignores in tests
  "PLR0915", # allow complex statements in tests
  "S603",    # allow subprocess with untrusted input in tests
  "S607",    # allow partial executable paths in tests
  "T201",    # allow print in tests
]
"examples/**/*.py" = [
  "T201",   # allow print in examples
  "INP001", # allow implicit namespace packages
]
"scripts/**/*.py" = [
  "T201",    # allow print in scripts
  "INP001",  # allow implicit namespace packages
  "S603",    # allow subprocess check with untrusted input
  "S607",    # allow starting a process with a partial executable path
  "PLC0415", # allow local imports (e.g. dynamic imports in build_docs)
  "BLE001",  # allow catching blind Exception
]
"docs/**/*.py" = [
  "T201",   # allow print in docs
  "INP001", # allow implicit namespace packages
]

[tool.ruff.lint.isort]
known-first-party = ["gitversioned", "tests"]

[tool.pytest.ini_options]
addopts = "-s -vvv --cache-clear"
asyncio_mode = "auto"
markers = [
  "smoke: quick tests to check basic functionality",
  "sanity: detailed tests to ensure major functions work correctly",
  "regression: tests to ensure that new changes do not break existing functionality",
]
testpaths = ["tests"]

[tool.yamlfix]
line_length = 88
sequence_style = "block_style"
preserve_quotes = true

[tool.coverage.run]
patch = ["subprocess"]
```

## File: taplo.toml

```toml
# Copyright 2026 markurtz
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include = [
  "*.toml",
  ".devcontainer/**/*.toml",
  ".github/**/*.toml",
  "docs/**/*.toml",
  "examples/**/*.toml",
  "scripts/**/*.toml",
  "src/**/*.toml",
  "tests/**/*.toml",
]
```

## File: .yamllint

```yaml
# Copyright 2026 markurtz
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

extends: default

ignore: |
  .venv/
  venv/
  target/
  build/
  dist/

rules:
  line-length:
    max: 300
  truthy:
    check-keys: false
  document-start: disable
```

# SECTION 2: CORE STRUCTURAL INTERFACES

## File: src/gitversioned/__init__.py

```python
"""
Opinionated PEP 440 Python versioning for Git repos and submodules.

Provides an automated, deterministic system for generating rich version information from
Git repository metadata. It enforces CI/User authority and creates version files with
deep metadata for auditability, integrating natively with Hatch and Setuptools.

Example:
::
    from gitversioned import Settings, resolve_version
    from gitversioned.utils import BuildEnvironment, GitRepository

    version, _, ref = resolve_version(
        Settings(), GitRepository(), BuildEnvironment()
    )
    print(f"Current version: {version}")
"""

from __future__ import annotations

from .logging import LoggingSettings, configure_logger
from .settings import Settings
from .versioning import (
    resolve_version,
    resolve_version_output,
    resolve_version_output_to_stream,
)

__all__ = [
    "LoggingSettings",
    "Settings",
    "__version__",
    "configure_logger",
    "resolve_version",
    "resolve_version_output",
    "resolve_version_output_to_stream",
]

__version__ = "0.1.3.dev10+9eea393"

configure_logger()
```

## File: src/gitversioned/logging.py

```python
"""
Configure logging infrastructure and provide utilities for GitVersioned.

This module initializes the global logger with custom configurations such as
log levels, target sinks, and thread-safe queues. It also provides automatic
function execution logging and integrates with OpenTelemetry trace contexts
for structured JSON output.
"""

from __future__ import annotations

import contextlib
import functools
import json
import sys
import traceback
from collections.abc import Callable
from typing import Any, ClassVar, Literal, TypeVar, cast, overload

from loguru import logger
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

from gitversioned.compat import opentelemetry_trace

__all__ = ["LoggingSettings", "autolog", "configure_logger", "logger"]

_LOG_ENTRY_FORMAT: str = "Calling function '{name}' with args={args}, kwargs={kwargs}"
_LOG_EXIT_FORMAT: str = "Function '{name}' returned: {result}"
_LOG_EXCEPTION_FORMAT: str = "Exception occurred in function '{name}': {exception}"

_FuncT = TypeVar("_FuncT", bound=Callable[..., Any])

_state: dict[str, int | None] = {"handler_id": None}


class LoggingSettings(BaseSettings):
    """
    Settings for configuring the loguru logging infrastructure.

    This Pydantic settings class loads variables prefixed with
    GITVERSIONED__LOGGING__ to manage log levels, destination sinks, thread
    queues, and OpenTelemetry format options.

    Example:
        >>> from gitversioned.logging import LoggingSettings, configure_logger
        >>> settings = LoggingSettings(level="DEBUG")
        >>> configure_logger(settings)

    model_config : ClassVar[SettingsConfigDict]
        Configuration dictionary dictating environment variable prefixes and
        nested delimiters.
    """

    enabled: bool = Field(
        default=False,
        description="Enables logging output across the gitversioned package.",
    )
    clear_loggers: bool = Field(
        default=False,
        description="Removes all existing active logger sinks prior to configuration.",
    )
    sink: str | Any = Field(
        default=sys.stdout,
        description=(
            "Specifies the output target (e.g. stdout, stderr, or a file path) "
            "for log messages."
        ),
    )
    level: str = Field(
        default="INFO",
        description="Sets the minimum severity level for logged messages.",
    )
    otel_formatting: Literal["auto", "enable", "disable"] = Field(
        default="auto",
        description="Enables JSON formatting compliant with OpenTelemetry.",
    )
    format: str | Callable[..., Any] | None = Field(
        default="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | "
        "<cyan>{name}</cyan>:<cyan>{function}</cyan> - <level>{message}</level>\n",
        description="Defines the standard text format template for emitted log lines.",
    )
    filter: Any = Field(
        default=True,
        description="Specifies a filter function or package prefix string.",
    )
    enqueue: bool = Field(
        default=True,
        description="Enables asynchronous, thread-safe message queueing.",
    )
    kwargs: dict[str, Any] = Field(
        default_factory=dict,
        description="Extra arguments passed directly to loguru's add handler.",
    )

    model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
        env_prefix="GITVERSIONED__LOGGING__",
        env_nested_delimiter="__",
    )

    @field_validator("sink", mode="before")
    @classmethod
    def _parse_sink(cls, value: Any) -> Any:
        # Convert string aliases for standard output/error streams to stream objects.
        if isinstance(value, str):
            mapping = {
                "stdout": sys.stdout,
                "sys.stdout": sys.stdout,
                "stderr": sys.stderr,
                "sys.stderr": sys.stderr,
            }
            return mapping.get(value.lower(), value)
        return value


def configure_logger(settings: LoggingSettings | None = None) -> None:
    """
    Configure the global loguru logger handler.

    Example:
        >>> configure_logger(LoggingSettings(level="WARNING"))

    :param settings: Logging configurations, defaults to None (loads from environment).
    :type settings: LoggingSettings | None
    :return: None
    :raises ImportError: Raised if OpenTelemetry formatting is enabled but
                         the package is not installed.
    """
    settings = settings or LoggingSettings()

    if not settings.enabled:
        logger.disable("gitversioned")
        return

    logger.enable("gitversioned")

    if settings.clear_loggers:
        logger.remove()
        _state["handler_id"] = None
    elif isinstance(_state["handler_id"], int):
        with contextlib.suppress(ValueError):
            logger.remove(_state["handler_id"])
        _state["handler_id"] = None

    use_otel = settings.otel_formatting == "enable" or (
        settings.otel_formatting == "auto" and opentelemetry_trace is not None
    )
    if settings.otel_formatting == "enable" and opentelemetry_trace is None:
        raise ImportError(
            "OpenTelemetry is not installed but 'otel_formatting' was set to 'enable'."
        )

    log_format = _otel_formatter if use_otel else settings.format
    filter_val = "gitversioned" if settings.filter is True else settings.filter

    if isinstance(filter_val, (list, tuple, str)):
        prefixes = (
            tuple(filter_val)
            if isinstance(filter_val, (list, tuple))
            else (filter_val,)
        )

        def final_filter(record: dict[str, Any]) -> bool:
            return bool(record["name"] and record["name"].startswith(prefixes))

    else:
        final_filter = None if filter_val is False else filter_val

    _state["handler_id"] = logger.add(
        cast("Any", settings.sink),
        level=settings.level,
        filter=cast("Any", final_filter),
        format=cast("Any", log_format),
        enqueue=settings.enqueue,
        **settings.kwargs,
    )


@overload
def autolog(func: _FuncT) -> _FuncT: ...


@overload
def autolog(
    func: None = None,
    *,
    exception_log_level: str | None = "ERROR",
) -> Callable[[_FuncT], _FuncT]: ...


def autolog(
    func: _FuncT | None = None,
    *,
    exception_log_level: str | None = "ERROR",
) -> _FuncT | Callable[[_FuncT], _FuncT]:
    """
    Decorate a function to log call inputs, outputs, and any raised exceptions.

    Examples:
        Use as a direct decorator:

        >>> @autolog
        ... def add(a: int, b: int) -> int:
        ...     return a + b

        Use as a decorator factory call with defaults:

        >>> @autolog()
        ... def sub(a: int, b: int) -> int:
        ...     return a - b

        Use with a custom exception log level:

        >>> @autolog(exception_log_level="WARNING")
        ... def divide(a: int, b: int) -> float:
        ...     return a / b

    :param func: Target function to wrap, defaults to None.
    :type func: Callable | None
    :param exception_log_level: Log level for exception reporting,
                                defaults to "ERROR".
    :type exception_log_level: str | None
    :return: The decorated wrapper or a decorator factory function.
    :rtype: Callable
    """

    def decorator(func_to_wrap: _FuncT) -> _FuncT:
        @functools.wraps(func_to_wrap)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            func_name = getattr(func_to_wrap, "__qualname__", "function")
            logger.debug(
                _LOG_ENTRY_FORMAT.format(name=func_name, args=args, kwargs=kwargs)
            )
            try:
                result = func_to_wrap(*args, **kwargs)
            except Exception as error:
                if exception_log_level == "ERROR":
                    logger.opt(exception=error).error(
                        _LOG_EXCEPTION_FORMAT.format(name=func_name, exception=error),
                    )
                elif exception_log_level is not None:
                    logger.log(
                        exception_log_level,
                        _LOG_EXCEPTION_FORMAT.format(name=func_name, exception=error),
                    )
                raise error
            else:
                logger.debug(_LOG_EXIT_FORMAT.format(name=func_name, result=result))
                return result

        return cast("_FuncT", wrapper)

    if func is None:
        return decorator
    return decorator(func)


def _otel_formatter(record: dict[str, Any]) -> str:
    # Format the log record as an OpenTelemetry compliant JSON string.
    trace_id = span_id = trace_flags = None

    if opentelemetry_trace:
        span = opentelemetry_trace.get_current_span()
        context = span.get_span_context()
        if context.is_valid:
            trace_id = format(context.trace_id, "032x")
            span_id = format(context.span_id, "016x")
            trace_flags = format(context.trace_flags, "02x")

    log_record = {
        "timestamp": record["time"].isoformat(),
        "severity_text": record["level"].name,
        "body": record["message"],
        "resource": {"service.name": "gitversioned"},
        "attributes": {
            "module": record["name"],
            "function": record["function"],
            "line": record["line"],
            "process_id": record["process"].id,
            **record["extra"],
        },
    }

    if record.get("exception"):
        exception = record["exception"]
        log_record["attributes"]["exception.type"] = exception.type.__name__
        log_record["attributes"]["exception.message"] = str(exception.value)
        log_record["attributes"]["exception.stacktrace"] = "".join(
            traceback.format_exception(
                exception.type, exception.value, exception.traceback
            )
        )

    if trace_id:
        log_record.update(
            {
                "trace_id": trace_id,
                "span_id": span_id,
                "trace_flags": trace_flags,
            }
        )

    # Escape braces so loguru doesn't interpret the JSON string as a format string
    # Escape '<' and '>' to prevent loguru from interpreting them as color markup tags
    return (
        json.dumps(log_record)
        .replace("{", "{{")
        .replace("}", "}}")
        .replace("<", "\\<")
        .replace(">", "\\>")
    ) + "\n"
```

## File: src/gitversioned/settings.py

```python
"""
Configuration management settings for GitVersioned.

This module resolves and manages configuration parameters loaded from CLI arguments,
environment variables, and files like ``pyproject.toml`` or ``setup.cfg``.
"""

from __future__ import annotations

import configparser
import contextlib
import functools
import re
from pathlib import Path
from typing import Annotated, Any, Literal, cast

from pydantic import BaseModel, Field, model_validator
from pydantic_settings import (
    BaseSettings,
    CliSettingsSource,
    PydanticBaseSettingsSource,
    PyprojectTomlConfigSettingsSource,
    SettingsConfigDict,
)

from gitversioned.compat import tomllib
from gitversioned.logging import autolog
from gitversioned.utils import EnsureList, EnsurePath

__all__ = [
    "IncrementLevel",
    "OutputStrategy",
    "RegexStrategy",
    "Settings",
    "SetupCfgSettingsSource",
    "TemplatePathStrategy",
    "TemplateStrStrategy",
    "VersionStandard",
    "VersionType",
]

VersionType = Annotated[
    Literal["auto", "release", "dev", "pre", "alpha", "nightly", "post"],
    "The type of version format to generate, driving version string construction.",
]
VersionStandard = Annotated[
    Literal["pep440", "semver2"],
    "The standard format used to normalize version strings.",
]
IncrementLevel = Annotated[
    Literal["major", "minor", "micro", "patch", "bug"],
    "The target segment level of the version to auto-increment.",
]


class TemplatePathStrategy(BaseModel):
    """
    Output strategy using a template file path.

    Resolves version files by reading a template file containing placeholder variables,
    replacing them with resolved version metadata, and writing to the output path.

    Example:
        ::

            strategy = TemplatePathStrategy(path=Path("templates/release.py.template"))
    """

    type: Literal["template_path"] = Field(
        default="template_path",
        description=(
            "Discriminator type field identifying the template path "
            "resolution strategy."
        ),
    )
    path: Path = Field(
        description=(
            "The file path containing the template text to format with "
            "version metadata."
        )
    )


class TemplateStrStrategy(BaseModel):
    """
    Output strategy using a raw template string.

    Formats the target version file utilizing an inline template string pattern
    defined directly in the configuration, rather than reading from a file.

    Example:
        ::

            strategy = TemplateStrStrategy(content="__version__ = '{version}'")
    """

    type: Literal["template_str"] = Field(
        default="template_str",
        description=(
            "Discriminator type field identifying the template string "
            "resolution strategy."
        ),
    )
    content: str = Field(
        description="The inline template string used to format the version file output."
    )


class RegexStrategy(BaseModel):
    """
    Output strategy using regex version replacement.

    Updates the version string inline in an existing file by searching for a match with
    a regex pattern and replacing the target named 'version' group.

    Example:
        ::

            strategy = RegexStrategy(pattern=r'version = "(?P<version>.*?)"')
    """

    type: Literal["regex"] = Field(
        default="regex",
        description=(
            "Discriminator type field identifying the regex-based replacement strategy."
        ),
    )
    pattern: str = Field(
        description=(
            "The regular expression containing a (?P<version>...) named group to "
            "locate and replace within the target file."
        )
    )


OutputStrategy = Annotated[
    TemplatePathStrategy | TemplateStrStrategy | RegexStrategy,
    (
        "The active output strategy configuration used to format and "
        "generate target version files."
    ),
    Field(
        discriminator="type",
        description=(
            "The active output strategy configuration used to format and "
            "generate target version files."
        ),
    ),
]


@autolog
def _detect_package_name(project_root: Path) -> str:
    # Detect package name from various config files or folder name.
    pyproject_path = project_root / "pyproject.toml"
    if pyproject_path.exists() and tomllib is not None:
        try:
            with pyproject_path.open("rb") as toml_file:
                data = tomllib.load(toml_file)
                name = data.get("project", {}).get("name")
                if name:
                    return str(name).replace("-", "_")
        except (OSError, ValueError):
            with contextlib.suppress(OSError, ValueError):
                content = pyproject_path.read_text(encoding="utf-8")
                match = re.search(r'(?m)^name\s*=\s*["\']([^"\']+)["\']', content)
                if match:
                    return match.group(1).replace("-", "_")

    setup_cfg_path = project_root / "setup.cfg"
    if setup_cfg_path.exists():
        with contextlib.suppress(OSError, configparser.Error):
            config = configparser.ConfigParser()
            config.read(setup_cfg_path)
            name = config.get("metadata", "name", fallback=None)
            if name:
                return name.replace("-", "_")

    return project_root.name.replace("-", "_")


@autolog
def _resolve_src_root(project_root: Path, package_name: str) -> Path:
    # Resolve src_root directory from project_root and package_name.
    src_pkg = project_root / "src" / package_name
    pkg_dir = project_root / package_name
    if src_pkg.exists() and src_pkg.is_dir():
        return src_pkg
    if pkg_dir.exists() and pkg_dir.is_dir():
        return pkg_dir
    return project_root


class SetupCfgSettingsSource(PydanticBaseSettingsSource):
    """
    Settings source for loading configurations from setup.cfg files.

    Extracts configuration parameters nested under the 'tool:gitversioned'
    sections of a project's setup.cfg file. Integrates as a custom source in
    the Pydantic settings management pipeline.

    Example:
        ::

            source = SetupCfgSettingsSource(Settings, project_root=Path.cwd())
            config = source()

    :ivar project_root: The root directory containing the setup.cfg file.
    """

    def __init__(self, settings_cls: type[BaseSettings], project_root: Path) -> None:
        """
        Initialize the setup.cfg settings source.

        :param settings_cls: The Settings class being configured.
        :param project_root: The root directory containing setup.cfg.
        """
        super().__init__(settings_cls)
        self.project_root = project_root

    def __call__(self) -> dict[str, Any]:
        """
        Retrieve loaded settings from setup.cfg.

        :return: Loaded configuration settings dict.
        """
        return self._config

    def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]:
        """
        Get value for a configuration field from setup.cfg.

        :param field: The Pydantic Field object.
        :param field_name: The name of the field to fetch.
        :return: A tuple containing the field's value, name, and if it was found.
        :raises KeyError: If the field is not present in the settings source.
        """
        _ = (field,)  # Allow unused variable to satisfy lint/format
        config = self._config
        if field_name in config:
            return config[field_name], field_name, False
        raise KeyError(field_name)

    @functools.cached_property
    def _config(self) -> dict[str, Any]:
        # Load and parse configuration from setup.cfg and cache the result.
        path = self.project_root / "setup.cfg"
        if not path.exists():
            return {}

        config_parser = configparser.ConfigParser()
        config_parser.read(path)
        base_section = "tool:gitversioned"

        result: dict[str, Any] = {}
        if base_section in config_parser:
            result.update(config_parser.items(base_section))

        prefix = f"{base_section}:"
        for section in config_parser.sections():
            if section.startswith(prefix):
                key = section[len(prefix) :]
                val = result.get(key, {})
                if not isinstance(val, dict):
                    val = {"_": val}

                val.update(config_parser.items(section))
                result[key] = val

        return result


class Settings(BaseSettings):
    """
    Unified configuration settings for GitVersioned.

    Manages settings loaded from environment variables, configuration files (such as
    pyproject.toml or setup.cfg), CLI flags, and constructor inputs. Governs how
    the dynamic version parser resolves git refs, matches tags, and generates target
    version files.

    Example:
        ::

            settings = Settings(package_name="my_package")
            src_path = settings.resolve_path_from_src("my_package/__init__.py")

    :cvar model_config: Custom configuration dictionary settings for Pydantic.
    """

    model_config = SettingsConfigDict(
        arbitrary_types_allowed=True,
        extra="ignore",
        populate_by_name=True,
        validate_assignment=True,
        env_prefix="GITVERSIONED__",
        cli_prefix="gitversioned_",
        cli_parse_args=True,
        pyproject_toml_table_header=("tool", "gitversioned"),
    )

    # Project Configuration
    package_name: str = Field(
        default="auto",
        description=(
            "The package name being versioned. "
            "Enables automatic package name detection when set to 'auto'."
        ),
    )
    project_root: EnsurePath = Field(
        default_factory=Path.cwd,
        description="The absolute path to the root directory of the project.",
    )
    src_root: EnsurePath = Field(
        default_factory=Path.cwd,
        description=(
            "The path to the source root directory. "
            "Enables automatic source directory fallback detection "
            "when set to 'auto'."
        ),
    )
    build_is_editable: bool = Field(
        default=False,
        description=(
            "Flag indicating whether the package is built as an editable installation."
        ),
    )

    # Version Source Configuration
    version: str = Field(
        default="auto",
        description=(
            "Explicit version override string. "
            "Enables dynamic version resolution from git/files "
            "when set to 'auto'."
        ),
    )
    source_type: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: ["auto"],
        description="Priority order of sources to query for version information.",
    )
    version_source_file: str | None = Field(
        default="version.txt",
        description=(
            "Path to a file containing the version string. Set to None to disable."
        ),
    )
    version_source_archive: str | None = Field(
        default=".git_archival.txt",
        description=(
            "Path to a git-archive export info file used when "
            "Git is unavailable. Set to None to disable."
        ),
    )
    version_source_function: str | None = Field(
        default=None,
        description=(
            "A string pointing to a module and function (e.g. 'module:func') "
            "to resolve the version. Set to None to disable."
        ),
    )
    regex_version: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$"
        ],
        description=(
            "Regular expression patterns to parse and validate "
            "the explicit version string."
        ),
    )
    regex_tag: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$"
        ],
        description=(
            "Regular expression patterns to extract semantic versioning from Git tags."
        ),
    )
    regex_branch: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
        ],
        description=(
            "Regular expression patterns to extract semantic "
            "versioning from the current Git branch name."
        ),
    )
    regex_commit: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"(?i)^(?:release\s+|bump(?:\s+\w+)*\s+)?"
            r"v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
        ],
        description=(
            "Regular expression patterns to extract semantic "
            "versioning from Git commit messages."
        ),
    )
    regex_file: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"(?i)(?:version|__version__)\s*[:=]\s*['\"]?"
            r"(v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:[a-zA-Z0-9.\-+]+)?)"
            r"['\"]?"
        ],
        description=(
            "Regular expression patterns to parse version strings "
            "from version source files."
        ),
    )
    regex_archive: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: [
            r"(?sm)"
            r"(?=.*^commit_sha:\s*(?P<commit_sha>[^\n]*))"
            r"(?=.*^short_sha:\s*(?P<short_sha>[^\n]*))"
            r"(?=.*^timestamp:\s*(?P<timestamp>[^\n]*))"
            r"(?=.*^author_name:\s*(?P<author_name>[^\n]*))"
            r"(?=.*^author_email:\s*(?P<author_email>[^\n]*))"
            r"(?=.*^ref_names:\s*(?P<ref_names>[^\n]*))"
            r"(?=.*^ref_names:.*?(?:v)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+))"
            r"(?=.*^distance_from_head:\s*(?P<distance_from_head>[^\n]*))"
            r"(?=.*^is_head_commit:\s*(?P<is_head_commit>[^\n]*))"
            r"(?=.*^total_commits:\s*(?P<total_commits>[^\n]*))"
            r"(?=.*^is_current_branch:\s*(?P<is_current_branch>[^\n]*))"
            r"(?=.*^commit_message:\n(?P<commit_message>.*))"
        ],
        description=(
            "Regular expression patterns to parse Git metadata "
            "from git-archive export files."
        ),
    )

    # Generated Version Configuration
    version_type: VersionType = Field(
        default="auto",
        description=(
            "The type of version format to generate (e.g. 'release', 'dev', or 'auto')."
        ),
    )
    version_standard: VersionStandard = Field(
        default="pep440",
        description=(
            "The standard formatting layout (PEP 440 or SemVer 2) "
            "to use for version normalization."
        ),
    )
    auto_increment: (
        dict[
            Literal["release", "dev", "pre", "alpha", "nightly", "post"],
            IncrementLevel,
        ]
        | None
    ) = Field(
        default=None,
        description=(
            "Target increment mapping to apply when ahead of the latest release tag."
        ),
    )
    format_main: str = Field(
        default="{version.major}.{version.minor}.{version.micro}",
        description="Format string for the main semantic version segment.",
    )
    format_dev: str = Field(
        default="dev{ref.timestamp:%Y%m%d}+{ref.short_sha}",
        description="Format string for development builds.",
    )
    format_pre: str = Field(
        default="a{ref.timestamp:%Y%m%d}",
        description="Format string for pre-release or alpha builds.",
    )
    format_post: str = Field(
        default="post{ref.distance_from_head}",
        description="Format string for post-release builds.",
    )
    dirty_ignore: Annotated[list[str], EnsureList()] = Field(
        default_factory=lambda: ["target", "build", "dist"],
        description=(
            "List of file paths and directories to ignore when "
            "checking if the repository is dirty."
        ),
    )

    # Generated Version Outputs Configuration
    output: str = Field(
        default="version.py",
        description="The target output path to write the generated version file.",
    )
    output_strategies: dict[str, OutputStrategy] | OutputStrategy = Field(
        default_factory=lambda: cast(
            "dict[str, OutputStrategy]",
            {
                "release": TemplatePathStrategy(
                    path=Path(__file__).parent / "templates" / "release.py.template",
                ),
                "dev": TemplatePathStrategy(
                    path=Path(__file__).parent / "templates" / "dev.py.template",
                ),
            },
        ),
        description="Output strategies for formatting the version file.",
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """
        Customize configuration sources and priority for loading settings.

        This method overrides the default pydantic-settings loaders to resolve
        values in the following order: constructor kwargs, setup.cfg,
        pyproject.toml, dotenv files, environment variables, and CLI arguments.

        :param settings_cls: The BaseSettings subclass being initialized.
        :param init_settings: Source loading constructor keyword arguments.
        :param env_settings: Source loading environment variables.
        :param dotenv_settings: Source loading .env files.
        :param file_secret_settings: Source loading file secrets.
        :return: A tuple of settings sources in priority order.
        """
        _ = (file_secret_settings,)  # Allow unused variable to satisfy lint/format
        input_args = init_settings()
        project_root = input_args.get("project_root") or Path.cwd()

        return (
            init_settings,
            SetupCfgSettingsSource(settings_cls, project_root=project_root),
            PyprojectTomlConfigSettingsSource(
                settings_cls, toml_file=project_root / "pyproject.toml"
            ),
            dotenv_settings,
            env_settings,
            CliSettingsSource(
                settings_cls,
                cli_ignore_unknown_args=True,
                cli_parse_args=True,
                cli_prefix=settings_cls.model_config.get("cli_prefix", ""),
            ),
        )

    def __str__(self) -> str:
        """
        Return a concise string representation of the settings.

        :return: Concise string representation.
        """
        return (
            f"{self.__class__.__name__}("
            f"package_name={self.package_name!r}, "
            f"version={self.version!r}, "
            f"version_type={self.version_type!r}, "
            f"project_root={self.project_root!r}, "
            f"src_root={self.src_root!r}, "
            f"source_type={self.source_type!r}, "
            f"auto_increment={self.auto_increment!r}, "
            f"output={self.output!r}, "
            f"dirty_ignore={self.dirty_ignore!r}"
            f")"
        )

    def __repr__(self) -> str:
        """
        Return a detailed string representation of the settings.

        :return: Detailed string representation of settings.
        """
        return (
            f"{self.__class__.__name__}("
            f"package_name={self.package_name!r}, "
            f"project_root={self.project_root!r}, "
            f"src_root={self.src_root!r}, "
            f"build_is_editable={self.build_is_editable!r}, "
            f"version={self.version!r}, "
            f"source_type={self.source_type!r}, "
            f"version_source_file={self.version_source_file!r}, "
            f"version_source_archive={self.version_source_archive!r}, "
            f"version_source_function={self.version_source_function!r}, "
            f"regex_version={self.regex_version!r}, "
            f"regex_tag={self.regex_tag!r}, "
            f"regex_branch={self.regex_branch!r}, "
            f"regex_commit={self.regex_commit!r}, "
            f"regex_file={self.regex_file!r}, "
            f"regex_archive={self.regex_archive!r}, "
            f"version_type={self.version_type!r}, "
            f"version_standard={self.version_standard!r}, "
            f"auto_increment={self.auto_increment!r}, "
            f"format_main={self.format_main!r}, "
            f"format_dev={self.format_dev!r}, "
            f"format_pre={self.format_pre!r}, "
            f"format_post={self.format_post!r}, "
            f"dirty_ignore={self.dirty_ignore!r}, "
            f"output={self.output!r}, "
            f"output_strategies={self.output_strategies!r}"
            f")"
        )

    @autolog
    def resolve_path_from_root(
        self, path: str | Path | None, enforce_existence: bool = True
    ) -> Path | None:
        """
        Resolve a path relative to the project root or source root.

        This method attempts to resolve the given path first from the project root,
        falling back to the source root if it is not found.

        Example:
            ::

                path = settings.resolve_path_from_root("version.txt")

        :param path: The path to resolve.
        :param enforce_existence: Whether to enforce that the path exists.
        :return: The resolved absolute Path if it exists (or if not enforcing
            existence), otherwise None.
        """
        if enforce_existence:
            return self.resolve_path_from_project(
                path, enforce_existence=True
            ) or self.resolve_path_from_src(path, enforce_existence=True)
        else:
            resolved = self.resolve_path_from_project(
                path, enforce_existence=True
            ) or self.resolve_path_from_src(path, enforce_existence=True)
            if resolved is not None:
                return resolved
            return self.resolve_path_from_project(path, enforce_existence=False)

    @autolog
    def resolve_path_from_project(
        self, path: str | Path | None, enforce_existence: bool = True
    ) -> Path | None:
        """
        Resolve a path relative to the project root directory.

        Example:
            ::

                path = settings.resolve_path_from_project("setup.cfg")

        :param path: The path to resolve.
        :param enforce_existence: Whether to enforce that the path exists.
        :return: The resolved absolute Path if it exists (or if not enforcing
            existence), otherwise None.
        """
        if not path:
            return None

        if isinstance(path, str):
            path = Path(path)
        if not path.is_absolute():
            path = self.project_root / path
        return path if (not enforce_existence or path.exists()) else None

    @autolog
    def resolve_path_from_src(
        self, path: str | Path | None, enforce_existence: bool = True
    ) -> Path | None:
        """
        Resolve a path relative to the source root directory.

        Example:
            ::

                path = settings.resolve_path_from_src("my_package/__init__.py")

        :param path: The path to resolve.
        :param enforce_existence: Whether to enforce that the path exists.
        :return: The resolved absolute Path if it exists (or if not enforcing
            existence), otherwise None.
        """
        if not path:
            return None

        if isinstance(path, str):
            path = Path(path)
        if not path.is_absolute():
            path = self.src_root / path
        return path if (not enforce_existence or path.exists()) else None

    @model_validator(mode="after")
    def _resolve_auto_fields(self) -> Settings:
        # Internal validator to resolve 'auto' fields after initialization.
        if not self.project_root.exists():
            raise ValueError(
                f"Project root directory does not exist: {self.project_root}"
            )
        if not self.project_root.is_dir():
            raise ValueError(f"Project root is not a directory: {self.project_root}")

        if self.package_name == "auto":
            new_pkg_name = _detect_package_name(self.project_root)
            if new_pkg_name != self.package_name:
                self.package_name = new_pkg_name

        if self.src_root == self.project_root:
            new_src_root = _resolve_src_root(self.project_root, self.package_name)
            if new_src_root != self.src_root:
                self.src_root = new_src_root

        return self
```

## File: src/gitversioned/utils/__init__.py

```python
"""
Utility components and helpers for the gitversioned package.

Provides foundational utilities such as Git repository abstractions, environment
metadata gathering, and Pydantic type coercions. Designed for consistent, typed,
and testable interfaces across the core application logic.

Example:
    .. code-block:: python

        from gitversioned.utils import BuildEnvironment, GitRepository

        repo = GitRepository(".")
        env = BuildEnvironment()
"""

from __future__ import annotations

from .environment import BuildEnvironment, get_ci_info, get_user
from .git import GitReference, GitRepository, NotAGitRepositoryError
from .pydantic import (
    EnsureBool,
    EnsureList,
    EnsurePath,
    coerce_bool,
    coerce_list,
    coerce_path,
)

__all__ = [
    "BuildEnvironment",
    "EnsureBool",
    "EnsureList",
    "EnsurePath",
    "GitReference",
    "GitRepository",
    "NotAGitRepositoryError",
    "coerce_bool",
    "coerce_list",
    "coerce_path",
    "get_ci_info",
    "get_user",
]
```

## File: src/gitversioned/utils/git.py

```python
"""Git repository utility module.

This module exposes a Pydantic-based interface to extract Git repository
metadata (commits, tags, and branches) via command-line subprocesses.

Example
-------
.. code-block:: python

    from gitversioned.utils.git import GitRepository

    repo = GitRepository()
    if repo.is_available:
        print(repo.current_branch.branch_name)
"""

from __future__ import annotations

import shlex
import subprocess
import sys
from collections.abc import Iterator
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field, model_validator

from gitversioned.logging import logger

__all__ = [
    "GitReference",
    "GitRepository",
    "NotAGitRepositoryError",
]

_EXPECTED_LOG_PARTS_COUNT = 7


class NotAGitRepositoryError(Exception):
    """Exception raised when a directory is not a valid Git repository.

    This error is raised when Git operations are performed on a directory
    that is not inside a valid Git work tree.

    Example
    -------
    .. code-block:: python

        try:
            root = GitRepository("/tmp").root_directory
        except NotAGitRepositoryError:
            pass
    """


class GitReference(BaseModel):
    """Pydantic model representing a Git reference (commit, tag, or branch).

    Provides the core metadata fields representing a Git reference in a repository
    with details for tag, branch, or commit types.

    Example
    -------
    .. code-block:: python

        from gitversioned.utils.git import GitReference

        ref = GitReference(short_sha="a1b2c3d", distance_from_head=0)
        print(ref.short_sha)
    """

    commit_sha: str = Field(
        description="Full Git commit SHA-1 hash to identify the object.",
        default="",
    )
    short_sha: str = Field(
        description="Abbreviated Git commit SHA hash for short display.",
        default="",
    )
    timestamp: datetime = Field(
        description="Creation or commit timestamp of the Git object.",
        default=datetime.min,
    )
    distance_from_head: int = Field(
        description="Commit distance from the current HEAD.",
        default=sys.maxsize,
    )
    is_head_commit: bool = Field(
        description="True if this is the HEAD commit.",
        default=False,
    )
    total_commits: int = Field(
        description="Total commit count of the repository.",
        default=0,
    )
    author_name: str = Field(description="Name of the commit author.", default="")
    author_email: str = Field(
        description="Email of the commit author.",
        default="",
    )
    commit_message: str = Field(
        description="Full commit message body and subject.", default=""
    )
    tag_name: str = Field(description="Name of the Git tag.", default="")
    branch_name: str = Field(description="Name of the Git branch.", default="")
    is_current_branch: bool = Field(
        description="True if the branch is currently checked out.",
        default=False,
    )

    @model_validator(mode="before")
    @classmethod
    def parse_git_references(cls, data: Any) -> Any:
        """Extract branch and tag metadata from input dictionary ref strings.

        This validator parses command output references to identify current
        branches and tags.

        :param data: The input dictionary or raw data to validate.
        :return: The parsed and normalized dictionary.
        """
        if not isinstance(data, dict):
            return data

        if "ref_names" in data:
            data["refs"] = data["ref_names"]

        if "refs" not in data:
            return data

        reference_string = data["refs"]
        reference_parts = [part.strip() for part in reference_string.split(",")]
        found_tags = []

        for part in reference_parts:
            # Detect current branch from 'HEAD -> branch_name'
            if "HEAD ->" in part:
                data["branch_name"] = part.replace("HEAD ->", "").strip()
                data["is_current_branch"] = True

            # Detect tags
            elif "tag:" in part:
                tag_content = part.replace("tag:", "").strip()
                found_tags.append(tag_content)

            # Fallback for plain branch names if HEAD was not explicitly indicated
            elif not data.get("branch_name") and not part.startswith("tag:"):
                data["branch_name"] = part

        # The first tag in the ref list is considered the closest/most recent
        if found_tags and not data.get("tag_name"):
            data["tag_name"] = found_tags[0]

        return data

    def __str__(self) -> str:
        time_str = self.timestamp.isoformat()
        if self.tag_name:
            return f"{self.tag_name} -> {self.short_sha} ({time_str})"
        if self.branch_name:
            marker = "*" if self.is_current_branch else " "
            return f"{marker} {self.branch_name} -> {self.short_sha} ({time_str})"
        if self.commit_message:
            return (
                f"{self.short_sha} {self.commit_message} - {self.author_name} "
                f"({time_str})"
            )
        return f"{self.short_sha} ({time_str})"


class GitRepository:
    """Interface for querying Git repository status and references.

    Provides properties and methods to interact with a Git repository using typed
    Pydantic models for commits, tags, and branches.

    Example
    -------
    .. code-block:: python

        from gitversioned.utils.git import GitRepository

        repo = GitRepository()
        if repo.is_available:
            print(repo.head_name)
    """

    def __init__(
        self,
        repository_path: Path | str | None = None,
    ) -> None:
        """Initialize the GitRepository instance.

        :param repository_path: Base directory of the repository,
            defaults to Path.cwd().
        """
        self.base_path = Path(repository_path or Path.cwd()).resolve()

    def __str__(self) -> str:
        """Return a concise string representation."""
        if not self.is_available:
            return f"GitRepository({self.base_path}) - Unavailable"

        dirty_files = self.dirty_files
        current = self.current_commit
        tag = self.last_tag
        branch = self.current_branch

        head = "detached"
        if branch:
            head = branch.branch_name
        elif current:
            head = current.short_sha

        return (
            f"GitRepository(path={self.base_path!r}, is_available=True, "
            f"commit_count={self.commit_count}, is_dirty={bool(dirty_files)}, "
            f"dirty_files={dirty_files}, "
            f"current_commit={current.short_sha if current else None}, "
            f"last_tag={tag.tag_name if tag else None}, "
            f"current_branch={branch.branch_name if branch else None}"
            f") - {head}{'*' if dirty_files else ''}"
        )

    def __repr__(self) -> str:
        """Return a detailed string representation."""
        return f"GitRepository(base_path={self.base_path!r})"

    @property
    def is_available(self) -> bool:
        """Check if the base path is within a valid Git work tree.

        :return: True if the repository path is a valid Git work tree, False otherwise.
        """
        return self._execute_command(["rev-parse", "--is-inside-work-tree"]) == "true"

    @property
    def root_directory(self) -> Path:
        """Get the root directory of the Git repository.

        :return: Absolute path to the Git repository root.
        :raises NotAGitRepositoryError: If the path is not a valid Git repository.
        """
        self._ensure_valid_repository()
        return Path(self._execute_command(["rev-parse", "--show-toplevel"]))

    @property
    def repository_name(self) -> str:
        """Get the name of the Git repository.

        Attempts to parse the name from the remote origin URL, falling back
        to the root directory name.

        :return: The repository name.
        """
        if remote_url := self.remote_origin_url:
            name = remote_url.split("/")[-1]
            return name[:-4] if name.endswith(".git") else name
        return self.root_directory.name

    @property
    def remote_origin_url(self) -> str:
        """Get the remote origin URL.

        :return: Remote origin URL, or empty string if not set.
        """
        return self._execute_command(["config", "--get", "remote.origin.url"])

    @property
    def commit_count(self) -> int:
        """Get the total commit count on the current branch.

        :return: Total number of commits, or 0 if unavailable.
        """
        if not self.is_available:
            return 0
        try:
            return int(self._execute_command(["rev-list", "--count", "HEAD"]) or 0)
        except ValueError:
            return 0

    @property
    def is_dirty(self) -> bool:
        """Check if the repository has uncommitted modifications.

        :return: True if dirty changes exist, False otherwise.
        """
        return bool(self.dirty_files)

    @property
    def dirty_files(self) -> list[Path]:
        """Get a list of all modified and untracked file paths.

        :return: List of paths with uncommitted changes.
        """
        output = self._execute_command(["status", "--porcelain"])
        dirty = []
        for line in output.splitlines():
            if line:
                path = line[3:]
                if " -> " in path:
                    path = path.split(" -> ")[-1]
                dirty.append((self.base_path / path).resolve())
        return dirty

    @property
    def current_commit(self) -> GitReference | None:
        """Get the most recent commit.

        :return: Most recent commit reference, or None if empty.
        """
        return next(self.commits, None)

    @property
    def current_commit_or_fallback(self) -> GitReference:
        """Get the most recent commit or a generated fallback reference.

        :return: Current commit reference, or a dummy reference if unavailable.
        """
        return (
            self.current_commit
            if self.is_available and self.current_commit
            else GitReference(
                timestamp=datetime.now(timezone.utc),
                distance_from_head=0,
                is_head_commit=True,
            )
        )

    @property
    def last_tag(self) -> GitReference | None:
        """Get the most recent tag.

        :return: Most recent tag reference, or None if no tags exist.
        """
        return next(self.tags, None)

    @property
    def current_branch(self) -> GitReference | None:
        """Get the currently checked-out branch.

        :return: Current branch reference, or None if in detached HEAD.
        """
        return next(
            (branch for branch in self.branches if branch.is_current_branch),
            None,
        )

    @property
    def head_name(self) -> str:
        """Get the branch name or the short commit SHA of HEAD.

        :return: Current branch name, or short SHA if detached.
        """
        if branch := self.current_branch:
            return branch.branch_name
        if current := self.current_commit:
            return current.short_sha
        return ""

    @property
    def commits(self) -> Iterator[GitReference]:
        """Yield all commits in the repository history.

        :return: Iterator of commit reference objects.
        :raises NotAGitRepositoryError: If the path is not a valid Git repository.
        """
        self._ensure_valid_repository()
        total_commits = self.commit_count
        format_string = "%H|%h|%cI|%an|%ae|%s|%D"
        lines = self._stream_command(["log", f"--format={format_string}"])

        for index, line in enumerate(lines):
            parts = line.split("|", 6)
            if len(parts) == _EXPECTED_LOG_PARTS_COUNT:
                tag_name = ""
                branch_name = ""
                is_current_branch = False

                refs = parts[6].split(", ") if parts[6] else []
                for ref in refs:
                    if ref.startswith("tag: "):
                        tag_name = ref[5:]
                    elif "->" in ref:
                        branch_name = ref.split(" -> ")[1]
                        is_current_branch = True
                    elif (
                        ref
                        and not ref.startswith("origin/")
                        and ref != "HEAD"
                        and not branch_name
                    ):
                        branch_name = ref

                yield GitReference(
                    commit_sha=parts[0],
                    short_sha=parts[1],
                    timestamp=datetime.fromisoformat(parts[2].replace("Z", "+00:00")),
                    author_name=parts[3],
                    author_email=parts[4],
                    commit_message=parts[5],
                    tag_name=tag_name,
                    branch_name=branch_name,
                    is_current_branch=is_current_branch,
                    distance_from_head=index,
                    is_head_commit=(index == 0),
                    total_commits=total_commits,
                )

    @property
    def tags(self) -> Iterator[GitReference]:
        """Yield all tags in the repository sorted by creation date.

        :return: Iterator of tag reference objects.
        :raises NotAGitRepositoryError: If the path is not a valid Git repository.
        """
        self._ensure_valid_repository()
        current = self.current_commit
        head_sha = current.commit_sha if current else ""
        total_commits = self.commit_count
        format_string = "%(refname:short)|%(creatordate:iso-strict)|%(objectname)"

        lines = self._stream_command(
            [
                "for-each-ref",
                "--sort=-creatordate",
                f"--format={format_string}",
                "refs/tags/",
            ]
        )

        for line in lines:
            name, date_str, sha = line.split("|")
            distance_str = self._execute_command(
                ["rev-list", "--count", f"{sha}..HEAD"]
            )
            yield GitReference(
                tag_name=name,
                commit_sha=sha,
                short_sha=sha[:7],
                timestamp=datetime.fromisoformat(date_str.replace("Z", "+00:00")),
                distance_from_head=int(distance_str or 0),
                is_head_commit=(sha == head_sha),
                total_commits=total_commits,
            )

    @property
    def branches(self) -> Iterator[GitReference]:
        """Yield all branches in the repository.

        :return: Iterator of branch reference objects.
        :raises NotAGitRepositoryError: If the path is not a valid Git repository.
        """
        self._ensure_valid_repository()
        current = self.current_commit
        head_sha = current.commit_sha if current else ""
        total_commits = self.commit_count
        format_string = (
            "%(refname:short)|%(objectname)|%(HEAD)|%(committerdate:iso-strict)"
        )

        lines = self._stream_command(
            [
                "for-each-ref",
                f"--format={format_string}",
                "refs/heads/",
                "refs/remotes/",
            ]
        )

        for line in lines:
            name, sha, current_marker, date_str = line.split("|")
            yield GitReference(
                branch_name=name,
                commit_sha=sha,
                short_sha=sha[:7],
                timestamp=datetime.fromisoformat(date_str.replace("Z", "+00:00")),
                distance_from_head=0,
                is_head_commit=(sha == head_sha),
                is_current_branch=(current_marker == "*"),
                total_commits=total_commits,
            )

    def filtered_dirty_files(
        self, ignore_paths: list[Path] | None = None, fail_on_unavailable: bool = False
    ) -> list[str]:
        """Filter modified files excluding the specified ignore paths.

        Example
        -------
        .. code-block:: python

            dirty = repo.filtered_dirty_files(ignore_paths=[Path("tmp")])

        :param ignore_paths: List of file/directory paths to exclude from results.
        :param fail_on_unavailable: If True, raise exception if Git is missing.
        :return: List of filtered dirty file paths as strings.
        :raises NotAGitRepositoryError: If repository is missing and
            fail_on_unavailable is True.
        """
        if not self.is_available:
            if fail_on_unavailable:
                raise NotAGitRepositoryError(
                    f"Path '{self.base_path}' is not a Git repository."
                )
            return []

        unfiltered_files = []
        ignore_paths_abs = [
            path.resolve() if path.is_absolute() else (self.base_path / path).resolve()
            for path in ignore_paths or []
        ]

        for dirty_file in self.dirty_files:
            if not any(
                dirty_file == ignored or ignored in dirty_file.parents
                for ignored in ignore_paths_abs
            ):
                unfiltered_files.append(str(dirty_file))

        return unfiltered_files

    def _stream_command(self, arguments: list[str]) -> Iterator[str]:
        # Stream the output of a Git command line by line.
        full_command = ["git", *arguments]
        try:
            with subprocess.Popen(  # noqa: S603
                full_command, cwd=self.base_path, stdout=subprocess.PIPE, text=True
            ) as process:
                if process.stdout:
                    for line in process.stdout:
                        if clean_line := line.strip():
                            yield clean_line
        except (subprocess.CalledProcessError, FileNotFoundError, OSError) as error:
            logger.debug(f"Command '{shlex.join(full_command)}' failed: {error}")

    def _execute_command(self, arguments: list[str]) -> str:
        # Execute a Git command and return stdout as a stripped string.
        full_command = ["git", *arguments]
        try:
            return subprocess.run(  # noqa: S603
                full_command,
                cwd=self.base_path,
                capture_output=True,
                text=True,
                check=True,
            ).stdout.rstrip()
        except (subprocess.CalledProcessError, FileNotFoundError, OSError) as error:
            logger.debug(f"Command '{shlex.join(full_command)}' failed: {error}")
            return ""

    def _ensure_valid_repository(self) -> None:
        # Ensure the repository is available, raising NotAGitRepositoryError if not.
        if not self.is_available:
            raise NotAGitRepositoryError(
                f"Path '{self.base_path}' is not a Git repository."
            )
```

## File: src/gitversioned/utils/environment.py

```python
"""
Build environment introspection utilities.

This module provides tools for extracting hardware, operating system, and
Continuous Integration (CI) metadata during the build process. It enables
reproducible and traceable builds by automatically capturing runtime
context into structured Pydantic models.
"""

from __future__ import annotations

import os
import platform
import socket
import uuid
from datetime import datetime, timezone
from pathlib import Path

from pydantic import BaseModel, ConfigDict, Field

from gitversioned.compat import psutil

__all__ = ["BuildEnvironment", "get_ci_info", "get_ram_gb", "get_user"]


def get_user() -> str:
    """
    Retrieve the current system or environment user.

    Attempts to use standard library OS queries first, falling back to
    common environment variables.

    Example:
        >>> get_user()
        'markkurtz'

    :return: The resolved username or "unknown" if undetermined.
    """
    try:
        return os.getlogin()
    except (OSError, AttributeError):
        return os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"


def get_ci_info() -> tuple[bool, str | None]:
    """
    Determine if the current execution is within a recognized Continuous
    Integration environment.

    Queries standard environment variables to identify platforms like
    GitHub Actions, GitLab CI, and others.

    Example:
        >>> is_ci, provider = get_ci_info()
        >>> print(f"CI: {is_ci}, Provider: {provider}")
        CI: True, Provider: GitHub Actions

    :return: Tuple indicating CI presence and provider name if found.
    """
    providers = {
        "GITHUB_ACTIONS": ("true", "GitHub Actions"),
        "GITLAB_CI": (None, "GitLab CI"),
        "CIRCLECI": ("true", "CircleCI"),
        "TRAVIS": ("true", "Travis CI"),
        "JENKINS_URL": (None, "Jenkins"),
        "BITBUCKET_COMMIT": (None, "Bitbucket Pipelines"),
    }
    for env_var, (expected, name) in providers.items():
        val = os.environ.get(env_var)
        if val and (expected is None or val == expected):
            return True, name

    if os.environ.get("CI") in ("true", "1", "True"):
        return True, "Unknown CI"
    return False, None


def get_ram_gb() -> float:
    """
    Calculate the total available system memory in gigabytes.

    Relies on `psutil` if available in the environment.

    Example:
        >>> get_ram_gb()
        16.0

    :return: The total RAM in GB, or 0.0 if `psutil` is unavailable.
    """
    if psutil:
        return round(psutil.virtual_memory().total / (1024**3), 2)
    return 0.0


class BuildEnvironment(BaseModel):
    """
    Structured metadata representing the current system and build execution context.

    Captures environmental data such as OS details, hardware specs, and CI presence.
    Utilized to record the provenance of a build artifact for auditing and debugging.

    Example:
        >>> env = BuildEnvironment()
        >>> print(env.os_system)
        'Darwin'
    """

    model_config = ConfigDict(frozen=True)

    # --- System & OS ---
    hostname: str = Field(
        default_factory=socket.gethostname,
        description="The network hostname of the build machine.",
    )
    user: str = Field(
        default_factory=get_user,
        description="The system username executing the build process.",
    )
    os_system: str = Field(
        default_factory=platform.system,
        description="The operating system name (e.g., 'Linux', 'Darwin', 'Windows').",
    )
    os_release: str = Field(
        default_factory=platform.release,
        description="The operating system release version.",
    )
    os_version: str = Field(
        default_factory=platform.version,
        description="The operating system build or release date string.",
    )

    # --- Hardware ---
    cpu_arch: str = Field(
        default_factory=platform.machine,
        description="Hardware architecture of the build machine (e.g., 'x86_64').",
    )
    cpu_cores: int = Field(
        default_factory=lambda: os.cpu_count() or 0,
        description="The number of logical CPU cores available.",
    )
    total_ram_gb: float = Field(
        default_factory=get_ram_gb,
        description="The total available system RAM in gigabytes.",
    )

    # --- Runtime ---
    python_version: str = Field(
        default_factory=platform.python_version,
        description="The version of the Python runtime executing the build.",
    )
    python_implementation: str = Field(
        default_factory=platform.python_implementation,
        description="The specific Python implementation (e.g., 'CPython', 'PyPy').",
    )
    python_compiler: str = Field(
        default_factory=platform.python_compiler,
        description="The compiler string used to build the Python runtime.",
    )
    timestamp: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="UTC timestamp when this context was captured.",
    )

    # --- CI Context ---
    is_ci: bool = Field(
        default_factory=lambda: get_ci_info()[0],
        description="True if executing within a recognized CI environment.",
    )
    ci_provider: str | None = Field(
        default_factory=lambda: get_ci_info()[1],
        description="The name of the detected CI provider, or None if undetermined.",
    )

    # --- Path Context ---
    project_root: Path = Field(
        default_factory=Path.cwd,
        description="The root directory of the project where the build was initiated.",
    )

    build_id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="A unique identifier generated for this specific build execution.",
    )

    def __str__(self) -> str:
        """Return a concise string representation."""

        ci_str = f" [CI: {self.ci_provider}]" if self.is_ci else " [Local]"
        return (
            f"BuildEnvironment({self.os_system} {self.os_release} {self.cpu_arch}, "
            f"Python {self.python_version}, project={self.project_root.name}, "
            f"id={self.build_id}){ci_str}"
        )

    def __repr__(self) -> str:
        """Return a detailed string representation."""
        return (
            f"BuildEnvironment("
            f"hostname={self.hostname!r}, user={self.user!r}, "
            f"os_system={self.os_system!r}, os_release={self.os_release!r}, "
            f"os_version={self.os_version!r}, "
            f"cpu_arch={self.cpu_arch!r}, cpu_cores={self.cpu_cores!r}, "
            f"total_ram_gb={self.total_ram_gb!r}, "
            f"python_version={self.python_version!r}, "
            f"python_implementation={self.python_implementation!r}, "
            f"python_compiler={self.python_compiler!r}, timestamp={self.timestamp!r}, "
            f"is_ci={self.is_ci!r}, ci_provider={self.ci_provider!r}, "
            f"project_root={self.project_root!r}, build_id={self.build_id!r}"
            f")"
        )
```

## File: src/gitversioned/utils/pydantic.py

```python
"""
Pydantic helpers for GitVersioned.

Provides reusable validation and type-coercion helpers for Pydantic models.
Integrates directly with the Pydantic core schema to handle robust parsing
of configurations and environment variables, including string-to-list splitting
and truthy/falsy string coercion.
"""

from __future__ import annotations

from collections.abc import Callable
from pathlib import Path
from typing import Annotated, Any, Generic, TypeVar, get_args, get_origin

from pydantic import BeforeValidator, Field, GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema

__all__ = [
    "EnsureBool",
    "EnsureList",
    "EnsurePath",
    "coerce_bool",
    "coerce_list",
    "coerce_path",
]

TypeVarT = TypeVar("TypeVarT")


def coerce_bool(value: Any) -> bool | Any:
    """
    Normalize truthy/falsy strings to actual booleans.

    Example:
        >>> coerce_bool("yes")
        True
        >>> coerce_bool("0")
        False
        >>> coerce_bool(5)
        5

    :param value: The value to coerce.
    :return: The boolean equivalent if recognized, otherwise the original value.
    """
    if isinstance(value, str):
        cleaned_value = value.lower().strip()
        if cleaned_value in {"true", "1", "yes", "t", "y"}:
            return True
        if cleaned_value in {"false", "0", "no", "f", "n"}:
            return False
    return value


def coerce_path(value: Any) -> Path | Any:
    """
    Normalize string paths to Path objects.

    Example:
        >>> isinstance(coerce_path("/tmp/path "), Path)
        True

    :param value: The value to coerce into a path.
    :return: A Path object if the input is a string, otherwise the original value.
    """
    if isinstance(value, str):
        return Path(value.strip())
    return value


def coerce_list(
    value: Any, item_pre_coercer: Callable[[Any], Any] | None = None
) -> list[Any]:
    """
    Recursively transform input into a list.

    Splits comma-separated strings and applies an optional pre-coercer function
    to individual items.

    Example:
        >>> coerce_list("a, b, c")
        ['a', 'b', 'c']
        >>> coerce_list("yes, no", coerce_bool)
        [True, False]

    :param value: The value to coerce into a list.
    :param item_pre_coercer: Optional function to apply to each item.
    :return: A list of processed items.
    """
    if value is None:
        return []

    if isinstance(value, str):
        items = [item.strip() for item in value.split(",") if item.strip()]
    elif isinstance(value, (list, tuple, set)):
        items = list(value)
    else:
        items = [value]

    return [
        coerce_list(item, item_pre_coercer)
        if isinstance(item, (list, tuple, set)) and not isinstance(item, str)
        else (item_pre_coercer(item) if item_pre_coercer else item)
        for item in items
    ]


class EnsureList(list[TypeVarT], Generic[TypeVarT]):
    """
    A list subclass integrating directly with Pydantic Core Schema.

    Preprocesses inputs (such as comma-separated strings and nested iterables)
    and applies inner type coercion before final schema validation.

    Example:
        >>> from pydantic import BaseModel
        >>> class MyModel(BaseModel):
        ...     items: EnsureList[int]
        >>> model = MyModel(items="1, 2, 3")
        >>> model.items
        [1, 2, 3]
    """

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        """
        Create a schema that hooks a pre-validator into the Pydantic pipeline.

        Extracts the inner type constraint and constructs a validator that runs
        prior to Pydantic's core schema validation.

        :param source_type: The original type annotation.
        :param handler: The Pydantic core schema handler.
        :return: The constructed Pydantic core schema.
        """
        origin = get_origin(source_type)
        if origin is Annotated:
            base_type = get_args(source_type)[0]
            base_origin = get_origin(base_type)
            if base_origin in (list, tuple, set):
                base_args = get_args(base_type)
                inner_type = base_args[0] if base_args else Any
            else:
                inner_type = base_type
        else:
            type_arguments = get_args(source_type)
            inner_type = type_arguments[0] if type_arguments else Any

        pre_coercer_map: dict[Any, Callable[[Any], Any]] = {
            bool: coerce_bool,
            Path: coerce_path,
        }
        target_pre_coercer = pre_coercer_map.get(inner_type)
        final_list_schema = handler.generate_schema(list[inner_type])

        def before_validator_logic(
            value: Any, _info: core_schema.ValidationInfo
        ) -> Any:
            # Preprocess the value through coerce_list with target pre-coercer.
            return coerce_list(value, item_pre_coercer=target_pre_coercer)

        return core_schema.with_info_before_validator_function(
            before_validator_logic,
            final_list_schema,
            serialization=core_schema.plain_serializer_function_ser_schema(
                list, when_used="json-unless-none"
            ),
        )


EnsureBool = Annotated[
    bool,
    BeforeValidator(coerce_bool),
    Field(
        description=(
            "Robust boolean type that coerces truthy/falsy strings into actual "
            "booleans, enabling flexible environment variable and config parsing."
        )
    ),
]

EnsurePath = Annotated[
    Path,
    BeforeValidator(coerce_path),
    Field(
        description=(
            "Robust Path type that coerces string paths into Path objects, "
            "enabling standardized filesystem references across configurations."
        )
    ),
]
```

## File: src/gitversioned/versioning/__init__.py

```python
"""
Core version resolution and output generation engine.

Provides entry points to resolve PEP 440 versions from Git repository state,
environment variables, and project settings. Exposes high-level functions to
resolve, format, and direct versions to targeted output files or streams.

Example:
    .. code-block:: python

        from gitversioned.settings import Settings
        from gitversioned.versioning import resolve_version_output_to_stream

        output_path, content, version, version_type, reference = (
            resolve_version_output_to_stream(Settings())
        )
"""

from __future__ import annotations

from .entrypoints import (
    resolve_version,
    resolve_version_output,
    resolve_version_output_to_stream,
)
from .sources import VersionResolutionError

__all__ = [
    "VersionResolutionError",
    "resolve_version",
    "resolve_version_output",
    "resolve_version_output_to_stream",
]
```

## File: src/gitversioned/versioning/entrypoints.py

```python
"""
Primary entry points for dynamic version resolution.

Provides high-level functions to resolve PEP 440 versions from Git repository
state, format the version string based on configured templates, and write the
output to target files or system streams.
"""

from __future__ import annotations

from pathlib import Path
from typing import cast

from packaging.version import Version

from gitversioned.logging import autolog, logger
from gitversioned.settings import Settings, VersionType
from gitversioned.utils import BuildEnvironment, GitReference, GitRepository
from gitversioned.versioning.generation import (
    generate_from_template,
    generate_output_from_strategies,
)
from gitversioned.versioning.sources import VersionResolutionError, resolve_sources

__all__ = [
    "resolve_version",
    "resolve_version_output",
    "resolve_version_output_to_stream",
]


@autolog
def resolve_version(
    settings: Settings,
    repository: GitRepository | None = None,
    environment: BuildEnvironment | None = None,
) -> tuple[Version, VersionType, GitReference]:
    """
    Resolve the dynamic version from configured sources and apply formatting.

    Queries the repository sources to obtain a base version, determines the build
    type (e.g., release or dev), applies auto-increment increments if configured,
    and formats the final PEP 440 version.

    Example:
        >>> from gitversioned.settings import Settings
        >>> version, v_type, ref = resolve_version(Settings())

    :param settings: Configuration settings governing version resolution.
    :param repository: Optional Git repository instance to query.
    :param environment: Optional build environment parameters.
    :return: A tuple containing the final normalized Version, the version type,
        and the GitReference used for resolution.
    """
    repository, environment = _resolve_repo_and_env(settings, repository, environment)

    try:
        version, reference = resolve_sources(settings.source_type, settings, repository)
        logger.info(f"Resolved version from sources: {version} for git ref {reference}")

    except VersionResolutionError as ver_err:
        logger.info(f"Could not resolve version from sources: {ver_err}")
        version = Version("0.1.0")
        reference = repository.current_commit_or_fallback
        logger.warning(
            "No version could be resolved from the configured sources, "
            f"defaulting to base version: {version} and git reference {reference}"
        )

    # Determine version type to build (release, dev, alpha, post)
    version_type = str(settings.version_type).strip().lower()
    if version_type == "auto":
        ignore_paths = [
            path
            for raw_path in (
                settings.output,
                settings.version_source_file,
                *settings.dirty_ignore,
            )
            if (path := settings.resolve_path_from_root(raw_path)) is not None
        ]
        is_dirty = bool(repository.filtered_dirty_files(ignore_paths=ignore_paths))
        version_type = "dev" if is_dirty or not reference.is_head_commit else "release"
        logger.info(
            f"Auto-resolved version type to: '{version_type}' for ref {reference}"
        )
    logger.info(f"Using version type: '{version_type}' for git reference {reference}")

    # Determine if an auto-increment should occur and if so apply it at the proper level
    auto_increment_level = ""
    if settings.auto_increment is not None:
        target_key = cast("VersionType", version_type)
        if target_key in settings.auto_increment:
            auto_increment_level = settings.auto_increment[target_key].lower().strip()
    if (
        target_idx := {"major": 0, "minor": 1, "micro": 2, "patch": 2, "bug": 2}.get(
            auto_increment_level
        )
    ) is not None:
        base_version = version
        parts = [version.major, version.minor, version.micro]
        parts[target_idx] += 1
        for index in range(target_idx + 1, len(parts)):
            parts[index] = 0
        version = Version(".".join(map(str, parts)))
        logger.info(
            f"Auto-incremented version from {base_version} to {version} "
            f"(target='{auto_increment_level}')"
        )

    # Generate and validate new semantic version
    version_base = generate_from_template(
        settings.format_main, version, reference, settings, repository, environment
    )
    version_segment = generate_from_template(
        {
            "release": "",
            "dev": settings.format_dev,
            "pre": settings.format_pre,
            "alpha": settings.format_pre,
            "nightly": settings.format_pre,
            "post": settings.format_post,
        }.get(version_type),
        version,
        reference,
        settings,
        repository,
        environment,
    )
    version_final = Version(f"{version_base}.{version_segment}".rstrip("+."))
    logger.info(f"Resolved final version: {version_final}")

    return version_final, cast("VersionType", version_type), reference


@autolog
def resolve_version_output(
    settings: Settings,
    repository: GitRepository | None = None,
    environment: BuildEnvironment | None = None,
) -> tuple[
    str,
    Version,
    VersionType,
    GitReference,
]:
    """
    Resolve the version and format the target output content.

    Runs version resolution and then applies the configured output strategies
    to format the final string (e.g., for writing to a version file).

    Example:
        >>> from gitversioned.settings import Settings
        >>> content, version, v_type, ref = resolve_version_output(Settings())

    :param settings: Configuration settings governing output generation.
    :param repository: Optional Git repository instance to query.
    :param environment: Optional build environment parameters.
    :return: A tuple containing the formatted output content string, the resolved
        Version, the version type, and the GitReference.
    """
    repository, environment = _resolve_repo_and_env(settings, repository, environment)
    version, version_type, reference = resolve_version(
        settings, repository, environment
    )
    output = generate_output_from_strategies(
        version, version_type, reference, settings, repository, environment
    )
    logger.info(f"Resolved version output: {output} for git reference {reference}")

    return output, version, version_type, reference


@autolog
def resolve_version_output_to_stream(
    settings: Settings,
    repository: GitRepository | None = None,
    environment: BuildEnvironment | None = None,
) -> tuple[Path | None, str, Version, VersionType, GitReference]:
    """
    Resolve the version and write formatted content to the configured output file path.

    Resolves the dynamic version, formats it using the configured strategy, and writes
    the formatted content to the specified output file path. Creates parent directories
    for files if needed.

    Example:
        >>> from gitversioned.settings import Settings
        >>> settings = Settings()
        >>> res = resolve_version_output_to_stream(settings)

    :param settings: Configuration settings governing output path and formatting.
    :param repository: Optional Git repository instance to query.
    :param environment: Optional build environment parameters.
    :return: A tuple containing the resolved output Path (or None if disabled),
        the formatted content, the Version, the version type, and the GitReference.
    :raises ValueError: If the configured output target cannot be written to.
    """
    repository, environment = _resolve_repo_and_env(settings, repository, environment)
    output_content, version, version_type, reference = resolve_version_output(
        settings, repository, environment
    )

    if not settings.output:
        logger.debug("No output target configured, skipping writing to output path.")
        return None, output_content, version, version_type, reference

    output_path = settings.resolve_path_from_root(
        settings.output, enforce_existence=False
    )
    if output_path is None:
        raise ValueError(f"Could not resolve output path for target: {settings.output}")
    try:
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(output_content, encoding="utf-8")
    except OSError as err:
        raise ValueError(f"Invalid output target: {settings.output}") from err

    return output_path, output_content, version, version_type, reference


def _resolve_repo_and_env(
    settings: Settings,
    repository: GitRepository | None = None,
    environment: BuildEnvironment | None = None,
) -> tuple[GitRepository, BuildEnvironment]:
    # Resolve the repository and environment to use.
    return (
        repository if repository is not None else GitRepository(settings.project_root),
        environment
        if environment is not None
        else BuildEnvironment(project_root=settings.project_root),
    )
```

# SECTION 3: INTERNAL IMPLEMENTATIONS

## File: src/gitversioned/__main__.py

```python
"""
CLI entry point for GitVersioned version resolution.

This module provides a standalone command-line interface for GitVersioned,
allowing developers and CI/CD pipelines to calculate versions, format output
strategies, and write version metadata to disk. It handles dynamic CLI parsing
by inspecting the Pydantic configuration schemas and maps subcommands directly
to version resolution workflows.

The main interface is the Typer application `app`, which exposes three primary
commands: `calculate`, `format`, and `write`. These commands dynamically adjust
their accepted parameters based on the `Settings` model fields, allowing
command-line overrides of any project-level configuration options.
"""

from __future__ import annotations

import contextlib
import inspect
import json
import sys
import typing
from typing import Annotated, Any

import typer
from loguru import logger
from pydantic_core import PydanticUndefined

from gitversioned import __version__
from gitversioned.logging import LoggingSettings, configure_logger
from gitversioned.settings import Settings
from gitversioned.utils import BuildEnvironment, GitRepository
from gitversioned.versioning import (
    resolve_version,
    resolve_version_output,
    resolve_version_output_to_stream,
)

__all__ = [
    "app",
    "calculate",
    "format_cmd",
    "main",
    "main_callback",
    "write",
]

app: Annotated[
    typer.Typer,
    (
        "The Typer application instance serving as the primary command-line "
        "entry point. Manages global options, subcommand routing, and "
        "auto-generates help text."
    ),
] = typer.Typer(
    add_completion=False,
    help="Opinionated PEP 440 Python versioning for Git repos and submodules.",
)


def main() -> None:
    """
    Run the Typer command-line application.

    .. code-block:: python

        from gitversioned.__main__ import main
        main()

    :returns: None.
    """
    app()


@app.callback(invoke_without_command=True)
def main_callback(
    ctx: typer.Context,
    version: Annotated[
        bool | None,
        typer.Option(
            "--version",
            "-v",
            is_eager=True,
            help="Show the version and exit.",
        ),
    ] = None,
) -> None:
    """
    Handle global command-line options and subcommand routing.

    :param ctx: Context object representing the current Typer execution flow.
    :param version: Flag to display the GitVersioned version and exit.
    :returns: None.
    :raises typer.Exit: When displaying version or printing help.
    """
    if version:
        typer.echo(f"gitversioned v{__version__}")
        raise typer.Exit
    if ctx.invoked_subcommand is None:
        typer.echo(ctx.get_help())
        raise typer.Exit


@app.command(name="calculate")
def calculate(**kwargs: Any) -> None:
    """
    Resolve and output only the PEP 440 version string.

    This command runs version resolution and prints the final calculated version
    string directly to stdout. It excludes output target and strategy settings.

    :param kwargs: Dynamic CLI override arguments mapping to Settings schema fields.
    :returns: None.
    """
    with _cli_execution_context("calculate", kwargs) as (
        settings,
        repository,
        environment,
    ):
        version, _, _ = resolve_version(
            settings=settings,
            repository=repository,
            environment=environment,
        )
        typer.echo(str(version))


@app.command(name="format")
def format_cmd(**kwargs: Any) -> None:
    """
    Resolve the version and output the formatted strategy templates.

    This command prints the rendered content from the configured version output
    strategies to stdout. It excludes final file write targets from the options.

    :param kwargs: Dynamic CLI override arguments mapping to Settings schema fields.
    :returns: None.
    """
    with _cli_execution_context("format", kwargs) as (
        settings,
        repository,
        environment,
    ):
        content, _, _, _ = resolve_version_output(
            settings=settings,
            repository=repository,
            environment=environment,
        )
        typer.echo(content, nl=False)


@app.command(name="write")
def write(**kwargs: Any) -> None:
    """
    Resolve the version and write output files.

    This command writes the rendered version templates to the configured file
    paths and prints a confirmation of the successfully written path to stdout.

    :param kwargs: Dynamic CLI override arguments mapping to Settings schema fields.
    :returns: None.
    """
    with _cli_execution_context("write", kwargs) as (
        settings,
        repository,
        environment,
    ):
        output_path, _, version, _, _ = resolve_version_output_to_stream(
            settings=settings,
            repository=repository,
            environment=environment,
        )

        logger.info(f"Successfully resolved version: {version}")
        if output_path:
            typer.echo(f"Version successfully written to {output_path}")


def _parse_cli_args(kwargs: dict[str, Any]) -> Settings:
    # Filter out None values and deserialize JSON strings for list/dict/model fields.
    cli_args = {key: value for key, value in kwargs.items() if value is not None}
    for field, value in cli_args.items():
        if isinstance(value, str):
            stripped = value.strip()
            if (stripped.startswith("[") and stripped.endswith("]")) or (
                stripped.startswith("{") and stripped.endswith("}")
            ):
                with contextlib.suppress(Exception):
                    cli_args[field] = json.loads(stripped)
    return Settings(**typing.cast("Any", cli_args))


@contextlib.contextmanager
def _cli_execution_context(
    command_name: str,
    kwargs: dict[str, Any],
) -> typing.Iterator[tuple[Settings, GitRepository, BuildEnvironment]]:
    # Provide a unified execution context for CLI subcommands.
    # Parses configuration, configures logging (ensuring stdout logger sinks are routed
    # to stderr to prevent interference with command output), initializes repo and build
    # environments, and handles all standard or unexpected errors/exits consistently.
    #
    # :param command_name: The name of the subcommand (for logging/errors).
    # :param kwargs: Raw CLI argument dictionary matching Settings schema.
    # :yields: A tuple of (Settings, GitRepository, BuildEnvironment) instances.
    try:
        settings = _parse_cli_args(kwargs)

        logging_settings = LoggingSettings()
        if logging_settings.sink is sys.stdout:
            logging_settings.sink = sys.stderr
        configure_logger(logging_settings)
        logger.debug(f"Starting gitversioned CLI {command_name}...")

        repository = GitRepository(settings.project_root)
        environment = BuildEnvironment(project_root=settings.project_root)
        yield settings, repository, environment

    except (typer.Exit, typer.Abort) as exit_exc:
        raise exit_exc
    except Exception as error:
        with contextlib.suppress(Exception):
            configure_logger(LoggingSettings(enabled=True, sink=sys.stderr))
        logger.exception(f"Failed to execute gitversioned CLI {command_name}")
        raise SystemExit(1) from error


def _build_cli_signature(exclude_fields: set[str] | None = None) -> inspect.Signature:
    # Dynamically build a CLI signature from the Settings model fields.
    exclude = exclude_fields or set()
    parameters = []
    for name, field in Settings.model_fields.items():
        if name in exclude:
            continue

        # Use None as default so kwargs only contains explicitly provided arguments.
        # This prevents Typer from overriding environment variables or config files
        # with Pydantic default values.
        default_val_repr: bool | str = False
        if field.default not in (..., PydanticUndefined, None):
            default_val_repr = (
                field.default if isinstance(field.default, bool) else str(field.default)
            )

        flag_name = f"--{name.replace('_', '-')}"
        if name == "version":
            flag_name = "--explicit-version"

        typer_option = typer.Option(
            None,
            flag_name,
            help=field.description,
            show_default=default_val_repr,
        )
        annotation = field.annotation
        if "dict" in str(annotation).lower():
            annotation = str

        parameters.append(
            inspect.Parameter(
                name,
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                default=typer_option,
                annotation=annotation,
            )
        )

    return inspect.Signature(parameters)


# Patch function signatures dynamically to expose Settings fields as
# CLI parameters to Typer.
typing.cast("Any", calculate).__signature__ = _build_cli_signature(
    exclude_fields={"output", "output_strategies"}
)
typing.cast("Any", format_cmd).__signature__ = _build_cli_signature(
    exclude_fields={"output"}
)
typing.cast("Any", write).__signature__ = _build_cli_signature()


if __name__ == "__main__":
    main()
```

## File: src/gitversioned/plugins/__init__.py

```python
"""Build system plugins for integrating gitversioned with Python build backends.

This package provides the integration plugins required to connect the core
git-versioned version resolution logic with standard Python build backends,
such as Hatchling and Setuptools. These plugins serve as build system hooks
and entry points that intercept the build lifecycle to query and inject
dynamically generated PEP 440 version strings into the distribution metadata.

Build systems discover and load these plugins using standardized packaging
entry points. The submodules handle the backend-specific API calls and map
the build configuration options defined in files like ``pyproject.toml`` to
the underlying versioning parameters.

Examples
--------
Configure Hatchling to use the gitversioned plugin within ``pyproject.toml``:

.. code-block:: toml

   [build-system]
   requires = ["hatchling", "git-versioned"]
   build-backend = "hatchling.build"

   [tool.hatch.version]
   source = "gitversioned"
"""
```

## File: src/gitversioned/plugins/hatchling_plugin.py

```python
"""Hatchling version source plugin for GitVersioned.

This module provides the Hatchling plugin interface for GitVersioned, allowing
Hatchling-based projects to resolve their dynamic package version directly from
the repository's Git history and environment state. It acts as a bridge between
Hatch's custom versioning hook framework and GitVersioned's core resolution and
generation logic.

The main plugin implementation is defined in the GitVersionedVersionSource
class, which is registered with Hatchling via the
hatch_register_version_source entry point.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from hatchling.metadata.core import ProjectMetadata
from hatchling.plugin import hookimpl
from hatchling.version.source.plugin.interface import VersionSourceInterface

from gitversioned.logging import LoggingSettings, autolog, configure_logger, logger
from gitversioned.settings import Settings
from gitversioned.utils import BuildEnvironment, GitRepository
from gitversioned.versioning import resolve_version_output_to_stream

__all__ = [
    "GitVersionedVersionSource",
    "hatch_register_version_source",
]


class GitVersionedVersionSource(VersionSourceInterface):
    """Hatchling version source interface for GitVersioned.

    This class implements the Hatchling VersionSourceInterface to resolve and
    manage project versions dynamically. It hooks into the Hatchling build
    lifecycle, retrieving version information from Git tags, commits, or
    environment variables and passing it to the package builder.

    By extending Hatchling's standard interface, it integrates directly with
    tools like Hatch build or Hatch publish, enabling zero-configuration
    version resolution for end users.

    .. code-block:: python

        from gitversioned.plugins.hatchling_plugin import GitVersionedVersionSource

        # Instantiate the plugin with project root and Hatch config
        source = GitVersionedVersionSource(root="/path/to/project", config={})
        version_info = source.get_version_data()

    :cvar PLUGIN_NAME: The registered name of this plugin within the Hatchling
        build system
    """

    PLUGIN_NAME: str = "gitversioned"

    def __init__(self, root: str, config: dict[str, Any]) -> None:
        """Initialize the GitVersioned version source plugin with project details.

        :param root: The project root directory path
        :param config: The plugin configuration dictionary from Hatchling
        """
        super().__init__(root, config)
        self._metadata: ProjectMetadata | None = None

    def get_version_data(self) -> dict[str, str]:
        """Compute the project version based on Git state and configuration.

        Resolves the version using the Git repository, build environment, and
        plugin configuration, and optionally writes the resolved version to a
        generated file if configured.

        .. code-block:: python

            version_data = source.get_version_data()
            version_str = version_data["version"]

        :returns: A dictionary containing the resolved version string mapped to
            the 'version' key
        :raises ValueError: If the version resolution process fails or cannot
            find a valid Git state
        """
        configure_logger(LoggingSettings(enabled=True))
        logger.debug("GitVersionedVersionSource.get_version_data called")

        resolved = os.environ.get("GITVERSIONED_RESOLVED_VERSION")
        if resolved:
            logger.info(f"Using resolved version from environment: {resolved}")
            return {"version": resolved}

        config = Settings(**self.get_settings_kwargs())
        repo = GitRepository(config.project_root)
        build_env = BuildEnvironment(project_root=config.project_root)
        output_path, _, version, _, _ = resolve_version_output_to_stream(
            settings=config,
            repository=repo,
            environment=build_env,
        )

        logger.info(
            f"gitversioned computed version {version} and wrote it to {output_path}"
        )

        return {"version": str(version)}

    def set_version(self, version: str, version_data: dict[str, Any]) -> None:
        """Set the project version manually, updating the version source file.

        This handler is invoked when writing a version via Hatch CLI (e.g.,
        `hatch version <version>`), persisting the version string to the
        configured file.

        .. code-block:: python

            source.set_version(version="1.2.3", version_data={})

        :param version: The raw version string to set
        :param version_data: Additional version context provided by Hatchling
        """
        logger.debug(
            "GitVersionedVersionSource.set_version called with "
            f"version='{version}', context={version_data}"
        )

        config = Settings(**self.get_settings_kwargs())
        if config.version_source_file:
            version_source_path = config.project_root / config.version_source_file
            version_source_path.write_text(f"version={version}\n", encoding="utf-8")

            logger.info(f"gitversioned set version {version} in {version_source_path}")
        else:
            logger.warning("version_source_file is not set; skipping manual update")

    @autolog
    def get_settings_kwargs(self) -> dict[str, Any]:
        """Extract and prepare the configuration dictionary for GitVersioned settings.

        Gathers the project metadata (root directory, package name, source root)
        and combines it with Hatch plugin-specific configurations to build
        keyword arguments.

        .. code-block:: python

            kwargs = source.get_settings_kwargs()

        :returns: A dictionary of configuration options compatible with
            GitVersioned settings
        """
        project_root = self.get_project_root()
        package_name = self.get_package_name()
        src_root = self.get_src_root()

        kwargs = {
            "package_name": package_name,
            "project_root": project_root,
            "src_root": src_root,
            "build_is_editable": False,
        }

        plugin_config = self.config.copy()
        plugin_config.pop("project_root", None)
        plugin_config.pop("src_root", None)

        kwargs.update(plugin_config)

        return kwargs

    def get_project_root(self) -> Path:
        """Resolve the absolute path to the project root directory.

        .. code-block:: python

            root_path = source.get_project_root()

        :returns: The resolved absolute path to the project root
        """
        return Path(self.root).resolve()

    def get_package_name(self) -> str:
        """Retrieve and normalize the package name from project metadata.

        Extracts the project name from the Hatchling project configuration and replaces
        hyphens with underscores for Python package compatibility.

        .. code-block:: python

            pkg_name = source.get_package_name()

        :returns: The normalized Python package name
        """
        metadata = self._get_metadata()
        return metadata.name.replace("-", "_")

    def get_src_root(self) -> Path:
        """Determine the source root directory for the project.

        Resolves the directory containing the source package by checking the
        explicit src_root configuration, Hatchling build target package paths,
        or falling back to directory layout conventions.

        .. code-block:: python

            src_root = source.get_src_root()

        :returns: The resolved Path to the source root directory
        """
        root = self.get_project_root()

        if "src_root" in self.config:
            return Path(root) / str(self.config["src_root"])

        metadata = self._get_metadata()
        hatch_config = (
            metadata.config.get("tool", {})
            .get("hatch", {})
            .get("build", {})
            .get("targets", {})
            .get("wheel", {})
        )

        packages = hatch_config.get("packages", None)
        if packages and isinstance(packages, list):
            return root / packages[0]

        sources = hatch_config.get("sources", None)
        if sources and isinstance(sources, dict):
            return root / str(list(sources.keys())[0])

        package_name = self.get_package_name()

        src_path = root / "src" / package_name
        if src_path.exists():
            return src_path

        pkg_path = root / package_name
        if pkg_path.exists():
            return pkg_path

        return root

    def _get_metadata(self) -> ProjectMetadata:
        if self._metadata is None:
            self._metadata = ProjectMetadata(str(self.get_project_root()), None)
        return self._metadata


@hookimpl
def hatch_register_version_source() -> type[VersionSourceInterface]:
    """Register the GitVersioned version source plugin with the Hatchling build system.

    Provides the entry point hook for Hatchling to locate and instantiate the custom
    GitVersionedVersionSource version source implementation.

    .. code-block:: python

        from gitversioned.plugins.hatchling_plugin import hatch_register_version_source

        plugin_type = hatch_register_version_source()

    :returns: The GitVersionedVersionSource class implementing VersionSourceInterface
    """
    return GitVersionedVersionSource
```

## File: src/gitversioned/plugins/setuptools_plugin.py

```python
"""
Setuptools build integration plugin for GitVersioned.

This module implements the integration layer between GitVersioned and the
Setuptools build system. It exposes hook entry points that Setuptools calls
during distribution configuration, allowing packaging configuration to
dynamically query and apply resolved Git-based versions.

The plugin functions by registering setup keyword arguments and finalizer
hooks. It extracts the package distribution options, resolves project
context, extracts any pre-existing or environment-specified versions,
executes the Git versioning resolution, and updates the distribution
metadata version dynamically. If configured, it also injects the generated
version file into the distribution's package_data or py_modules.
"""

from __future__ import annotations

import email
import os
from distutils.errors import DistutilsSetupError
from pathlib import Path
from typing import Any, cast

from packaging.utils import canonicalize_name
from setuptools import Distribution

from gitversioned.logging import LoggingSettings, autolog, configure_logger, logger
from gitversioned.settings import Settings
from gitversioned.utils import BuildEnvironment, GitRepository
from gitversioned.versioning import resolve_version_output_to_stream

__all__ = ["finalize_distribution_options", "setup_keywords"]

# Constants for internal validation
_INVALID_VERSIONS: set[str] = {"None", "0.0.0", "UNKNOWN"}


def setup_keywords(distribution: Distribution, attribute: str, value: Any) -> None:
    """
    Validate and store the GitVersioned configuration dictionary.

    Registers the `gitversioned` configuration dictionary on the distribution object.
    This configuration is subsequently retrieved during option finalization to customize
    the version resolution process.

    .. code-block:: python

        # Example usage inside setup.py:
        setup(
            gitversioned={
                "version_source_file": "version.txt",
                "source_type": ["tag", "file"],
            },
        )

    :param distribution: The Setuptools distribution object being configured.
    :param attribute: The name of the setup keyword (must be "gitversioned").
    :param value: The configuration settings dictionary provided in the setup call.
    :raises DistutilsSetupError: If the keyword attribute is invalid or the
        value is not a dict.
    """
    configure_logger(LoggingSettings(enabled=True))
    logger.debug(f"setup_keywords called with attribute='{attribute}'")

    if attribute != "gitversioned":
        logger.error(f"Unknown keyword argument: {attribute}")
        raise DistutilsSetupError(f"Unknown keyword argument: {attribute}")

    if not isinstance(value, dict):
        logger.error("gitversioned keyword argument must be a dict")
        raise DistutilsSetupError("gitversioned must be a dict")

    cast("Any", distribution).gitversioned_config = value


def finalize_distribution_options(distribution: Distribution) -> None:
    """
    Compute the package version and update the distribution metadata.

    This is the primary entry point triggered during the Setuptools distribution
    finalization lifecycle. It resolves the package name, extracts any established or
    environment-provided version, reads configuration overrides, calculates the dynamic
    version using Git metadata, and applies the version to the distribution metadata.
    If a version file is generated, it is automatically injected into the distribution's
    package data or module list.

    .. code-block:: python

        # Hook is registered as a Setuptools entry point:
        # entry_point = "gitversioned.plugins.setuptools_plugin"
        # func = "finalize_distribution_options"

    :param distribution: The Setuptools distribution object to finalize.
    :raises DistutilsSetupError: If the package name is unresolved or if version
        resolution encounters an unexpected failure.
    """
    configure_logger(LoggingSettings(enabled=True))
    logger.debug("Finalizing distribution options for GitVersioned.")

    project_root, source_root, package_name = _resolve_project_context(distribution)
    if not package_name:
        raise DistutilsSetupError("Could not determine package name.")

    # Check for an established version to avoid redundant Git resolution
    established_version = _extract_established_version(distribution, project_root)

    resolved = os.environ.get("GITVERSIONED_RESOLVED_VERSION")
    if resolved and not established_version:
        established_version = resolved

    config_overrides = getattr(distribution, "gitversioned_config", {})

    try:
        kwargs: Any = {
            "package_name": package_name,
            "project_root": project_root,
            "src_root": source_root,
            "build_is_editable": getattr(distribution, "editable", False),
        }
        kwargs.update(config_overrides)
        settings = Settings(**kwargs)

        if established_version:
            logger.info(f"Using established version: {established_version}")
            version_string = established_version
            output_path = _find_existing_version_file(settings)
        else:
            repository = GitRepository(settings.project_root)
            environment = BuildEnvironment(project_root=settings.project_root)
            output_path, _, version, _, _ = resolve_version_output_to_stream(
                settings=settings, repository=repository, environment=environment
            )
            version_string = str(version)

        # Update distribution metadata
        if hasattr(distribution, "metadata"):
            distribution.metadata.version = version_string

        if output_path and isinstance(output_path, Path):
            _inject_output_into_distribution(
                distribution=distribution,
                output_path=output_path,
                source_root=source_root,
                package_name=package_name,
            )

    except Exception as error:
        if isinstance(error, DistutilsSetupError):
            raise
        logger.exception("Unexpected failure during version resolution")
        raise DistutilsSetupError(f"Failed to resolve version: {error}") from error


@autolog
def _extract_established_version(
    distribution: Distribution, project_root: Path
) -> str | None:
    # Check metadata, distribution, and PKG-INFO for an existing valid version.
    candidates = [
        getattr(distribution.metadata, "version", None),
        getattr(distribution, "version", None),
    ]

    pkg_info_path = project_root / "PKG-INFO"
    if pkg_info_path.is_file():
        try:
            with pkg_info_path.open(encoding="utf-8") as file_handle:
                message = email.message_from_file(file_handle)
                candidates.append(message.get("Version"))
        except (OSError, ValueError) as error:
            logger.warning(f"Failed to read PKG-INFO: {error}")

    for version in candidates:
        if (
            isinstance(version, str)
            and version.strip()
            and version not in _INVALID_VERSIONS
        ):
            return version.strip()
    return None


@autolog
def _find_existing_version_file(settings: Settings) -> Path | None:
    # Locate the existing version file if resolution is skipped.
    if not settings.output:
        return None
    output_path = Path(settings.output)
    if not output_path.is_absolute():
        output_path = settings.src_root / output_path
    return output_path if output_path.exists() else None


@autolog
def _resolve_project_context(
    distribution: Distribution,
) -> tuple[Path, Path, str | None]:
    # Determines project root, source root, and package name via waterfall logic.
    project_root = Path(getattr(distribution, "src_root", None) or Path.cwd())
    package_name = None

    # Priority 1: Direct metadata
    name_raw = getattr(distribution.metadata, "name", None)
    if not name_raw or name_raw == "UNKNOWN":
        name_raw = distribution.get_name()

    if name_raw and name_raw != "UNKNOWN":
        package_name = canonicalize_name(name_raw).replace("-", "_")

    # Priority 2: Packages list
    if not package_name:
        packages = getattr(distribution, "packages", None)
        if packages and isinstance(packages, (list, tuple)):
            package_name = packages[0]

    # Resolve source root and fallback if name is still missing
    if package_name:
        source_root = _get_source_root(project_root, distribution, package_name)
    else:
        probe = _probe_filesystem_context(project_root)
        source_root, package_name = probe if probe else (project_root, None)

    return project_root, source_root, package_name


def _get_source_root(
    project_root: Path, distribution: Distribution, package_name: str
) -> Path:
    # Maps the package name to its source directory using package_dir configuration.
    package_dir = getattr(distribution, "package_dir", None) or {}

    relative_source = package_dir.get(package_name)
    if relative_source is None and "_" in package_name:
        relative_source = package_dir.get(package_name.replace("_", "-"))

    if relative_source is None:
        relative_source = package_dir.get("", "")

    base_path = project_root / relative_source
    return (
        base_path / package_name if (base_path / package_name).is_dir() else base_path
    )


def _probe_filesystem_context(project_root: Path) -> tuple[Path, str] | None:
    # Probes the filesystem for a package directory containing an __init__.py.
    for search_path in (project_root / "src", project_root):
        if not search_path.is_dir():
            continue
        for item in search_path.iterdir():
            if item.is_dir() and (item / "__init__.py").exists():
                return item, item.name
    return None


@autolog
def _inject_output_into_distribution(
    distribution: Distribution,
    output_path: Path,
    source_root: Path,
    package_name: str,
) -> None:
    # Registers the generated file in the distribution's package_data or py_modules.
    # Attempt 1: Package data (internal file)
    package_folder = (
        source_root if source_root.name == package_name else source_root / package_name
    )
    try:
        relative_output = str(output_path.relative_to(package_folder))
        current_packages = getattr(distribution, "packages", None)
        if current_packages is None:
            distribution.packages = [package_name]
        elif package_name not in current_packages:
            distribution.packages = list(current_packages) + [package_name]

        if getattr(distribution, "package_data", None) is None:
            distribution.package_data = {}
        distribution.package_data.setdefault(package_name, []).append(relative_output)
        return
    except ValueError:
        pass

    # Attempt 2: Flat module
    try:
        relative_module = str(output_path.relative_to(source_root))
        if "/" not in relative_module and output_path.suffix == ".py":
            modules = getattr(distribution, "py_modules", []) or []
            if output_path.stem not in modules:
                modules.append(output_path.stem)
            distribution.py_modules = modules
            return
    except ValueError:
        pass

    logger.warning(f"Version file {output_path} is outside source root {source_root}")
```

## File: src/gitversioned/versioning/generation.py

```python
"""Format and generate output version strings.

Provides utilities to format versions under standards like PEP 440 and SemVer 2,
render version templates using Git repository metadata, and execute strategies to
inject version strings into files or output streams.
"""

from __future__ import annotations

import re

from packaging.version import Version
from tstr import generate_template, render

from gitversioned.logging import autolog
from gitversioned.settings import (
    RegexStrategy,
    Settings,
    TemplatePathStrategy,
    TemplateStrStrategy,
    VersionType,
)
from gitversioned.utils import BuildEnvironment, GitReference, GitRepository

__all__ = [
    "FormattedVersion",
    "generate_from_template",
    "generate_output_from_strategies",
]


class FormattedVersion(Version):
    """Version subclass providing custom string formatting.

    Wraps a packaging Version to custom-format string output for PEP 440 and
    SemVer 2 standards based on the resolved version type.

    Example:
        >>> from packaging.version import Version
        >>> ver = FormattedVersion(Version("1.0.0.post1"), "post", "semver2")
        >>> str(ver)
        '1.0.0-post1'
    """

    def __init__(
        self,
        version: Version,
        version_type: VersionType,
        version_standard: str = "pep440",
    ) -> None:
        """Initialize the formatted version wrapper.

        :param version: The base packaging Version object.
        :param version_type: The type of version being represented.
        :param version_standard: The target version standard ("pep440" or "semver2").
        """
        super().__init__(str(version))
        self._version_type = version_type
        self._version_standard = version_standard

    def __str__(self) -> str:
        """Return the version formatted according to the standard.

        :return: The formatted version string.
        :raises ValueError: If the version standard is unsupported.
        """
        if self._version_standard == "pep440":
            return super().__str__()

        if self._version_standard == "semver2":
            formatted = ".".join(map(str, (list(self.release) + [0, 0])[:3]))
            if self._version_type == "post":
                post_value = self.post or 0
                formatted += f"-post{post_value}"
            elif self._version_type in ("pre", "alpha", "nightly"):
                pre_parts = self.pre or ("a", 0)
                formatted += f"{pre_parts[0]}{pre_parts[1]}"
            elif self._version_type == "dev":
                dev_value = self.dev or 0
                formatted += f"-dev{dev_value}"
            if self.local is not None:
                formatted += f"+{self.local}"
            return formatted

        raise ValueError(f"Unsupported version standard: {self._version_standard}")

    def __repr__(self) -> str:
        """Return the developer-friendly string representation of the formatted version.

        :return: The string representation.
        """
        return repr(self.__str__())


@autolog
def generate_from_template(
    pattern: str | None,
    version: Version,
    reference: GitReference,
    settings: Settings,
    repository: GitRepository,
    environment: BuildEnvironment,
) -> str:
    """Render a template pattern using version and git repository metadata.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import BuildEnvironment, GitReference, GitRepository
        >>> from packaging.version import Version
        >>> generate_from_template(
        ...     "v{version}-{ref.short_sha}",
        ...     Version("1.0.0"),
        ...     GitReference(short_sha="abc1234"),
        ...     Settings(),
        ...     GitRepository(),
        ...     BuildEnvironment()
        ... )
        'v1.0.0-abc1234'

    :param pattern: The template string pattern to render.
    :param version: The target Version object.
    :param reference: The active Git reference metadata.
    :param settings: Active application configuration.
    :param repository: The Git repository context helper.
    :param environment: The environment build variables context.
    :return: The rendered string, or an empty string if pattern is empty.
    """
    if not pattern:
        return ""

    context = {
        "version": version,
        "repo": repository,
        "config": settings,
        "env": environment,
        "ref": reference,
    }

    return str(render(generate_template(pattern, context, use_eval=True)))


@autolog
def generate_output_from_strategies(
    version: Version,
    version_type: VersionType,
    reference: GitReference,
    settings: Settings,
    repository: GitRepository,
    environment: BuildEnvironment,
) -> str:
    """Resolve the configuration output strategy to generate formatted version content.

    Example:
        >>> from packaging.version import Version
        >>> from gitversioned.settings import Settings, TemplateStrStrategy
        >>> from gitversioned.utils import BuildEnvironment, GitReference, GitRepository
        >>> settings = Settings(
        ...     output_strategies=TemplateStrStrategy(
        ...         content="__version__ = '{version}'"
        ...     )
        ... )
        >>> generate_output_from_strategies(
        ...     Version("1.0.0"),
        ...     "release",
        ...     GitReference(),
        ...     settings,
        ...     GitRepository(),
        ...     BuildEnvironment(),
        ... )
        "__version__ = '1.0.0'"

    :param version: The target Version object.
    :param version_type: The category of version being built.
    :param reference: The current Git reference metadata.
    :param settings: Active application configuration.
    :param repository: The Git repository context helper.
    :param environment: The environment build variables context.
    :return: The generated version string content.
    :raises ValueError: If the active strategy is unsupported or cannot be resolved.
    """
    if isinstance(
        settings.output_strategies,
        (TemplatePathStrategy, TemplateStrStrategy, RegexStrategy),
    ):
        strategy = settings.output_strategies
    elif isinstance(settings.output_strategies, dict):
        if len(settings.output_strategies) == 1:
            strategy = list(settings.output_strategies.values())[0]
        elif version_type in settings.output_strategies:
            strategy = settings.output_strategies[version_type]
        elif "release" in settings.output_strategies:
            strategy = settings.output_strategies["release"]
        else:
            raise ValueError("Could not determine output strategy.")
    else:
        raise ValueError("Could not determine output strategy.")

    if isinstance(strategy, (TemplateStrStrategy, TemplatePathStrategy)):
        return _generate_output_from_template_strategy(
            strategy,
            version,
            version_type,
            reference,
            settings,
            repository,
            environment,
        )

    if isinstance(strategy, RegexStrategy):
        return _generate_output_from_regex_strategy(
            strategy,
            version,
            version_type,
            reference,
            settings,
            repository,
            environment,
        )

    raise ValueError(f"Unsupported strategy type: {type(strategy)}")


@autolog
def _generate_output_from_template_strategy(
    strategy: TemplatePathStrategy | TemplateStrStrategy,
    version: Version,
    version_type: VersionType,
    reference: GitReference,
    settings: Settings,
    repository: GitRepository,
    environment: BuildEnvironment,
) -> str:
    # Generate content from a template file or template string strategy.
    if isinstance(strategy, TemplatePathStrategy):
        template_path = settings.resolve_path_from_root(strategy.path)
        if not template_path:
            raise FileNotFoundError(
                f"Template path '{strategy.path}' does not exist in project root "
                f"{settings.project_root} or src root {settings.src_root}."
            )
        content = template_path.read_text(encoding="utf-8")
        strategy = TemplateStrStrategy(content=content)

    if isinstance(strategy, TemplateStrStrategy):
        return generate_from_template(
            strategy.content,
            FormattedVersion(version, version_type, settings.version_standard),
            reference,
            settings,
            repository,
            environment,
        )

    raise ValueError(f"Invalid output strategy type: {type(strategy)}")


@autolog
def _generate_output_from_regex_strategy(
    strategy: RegexStrategy,
    version: Version,
    version_type: VersionType,
    reference: GitReference,
    settings: Settings,
    repository: GitRepository,
    environment: BuildEnvironment,
) -> str:
    # Inject the formatted version into an existing file based on a regex pattern.

    output_path = settings.resolve_path_from_root(settings.output)
    if not output_path:
        raise FileNotFoundError(
            f"Could not resolve content from {settings.output} in project root "
            f"for regex strategy {strategy}."
        )

    content = output_path.read_text(encoding="utf-8")
    matches = list(re.finditer(strategy.pattern, content, flags=re.MULTILINE))
    if not matches:
        raise ValueError(
            f"Regex pattern {strategy.pattern} not found in "
            f"output content from {output_path}."
        )

    # Process matches in reverse order so modification lengths
    # don't corrupt the index offsets of preceding matches.
    for match in reversed(matches):
        group_dict = match.groupdict()

        if group_dict:
            # Collect and sort named groups inside this single match by their start
            # index in reverse order to prevent inner-match offset shifting.
            named_groups = []
            for name, value in group_dict.items():
                if value is not None:
                    named_groups.append((name, match.start(name), match.end(name)))

            named_groups.sort(key=lambda item: item[1], reverse=True)

            for name, start_idx, end_idx in named_groups:
                # Convert group names like 'ref__tag' to 'ref.tag' for tstr evaluation
                tstr_expression = name.replace("__", ".")
                placeholder = f"{{{tstr_expression}}}"
                content = content[:start_idx] + placeholder + content[end_idx:]
        else:
            # Fallback: if no named capture groups exist, assume the
            # entire regex match represents the version string.
            start_idx, end_idx = match.span()
            content = content[:start_idx] + "{version}" + content[end_idx:]

    return _generate_output_from_template_strategy(
        TemplateStrStrategy(content=content),
        version,
        version_type,
        reference,
        settings,
        repository,
        environment,
    )
```

## File: src/gitversioned/versioning/sources.py

```python
"""Resolve project version from configured version sources.

This module provides functions to extract and resolve semantic versions from
explicit settings, files, Python functions, Git metadata (tags, branches, commits),
or archives. It processes these sources in a configurable priority order to find
and return a valid PEP 440 version.
"""

from __future__ import annotations

import functools
import importlib
import re as re_module
import sys
from typing import Any, Literal

from packaging.version import Version

from gitversioned.logging import autolog, logger
from gitversioned.settings import Settings
from gitversioned.utils import GitReference, GitRepository

__all__ = [
    "VersionResolutionError",
    "resolve_from_explicit_source",
    "resolve_from_file_source",
    "resolve_from_function_source",
    "resolve_from_git_source",
    "resolve_sources",
    "resolve_sources_from_archive",
]


class VersionResolutionError(ValueError):
    """Exception raised when version resolution fails for a source.

    Indicates that a configured version source (file, function, git, or archive)
    failed to resolve to a valid semantic version. Used to distinguish resolution
    failures from general runtime errors.

    Example:
        >>> raise VersionResolutionError("No tag matches version pattern.")
    """


@autolog(exception_log_level="INFO")
def resolve_sources(
    sources: list[str],
    settings: Settings,
    repository: GitRepository,
) -> tuple[Version, GitReference]:
    """Resolve project version by checking configured sources in order.

    Iterates through the requested sources (e.g., 'file', 'tag', 'branch') and
    returns the first resolved version and Git reference. If 'auto' is specified,
    it expands to check all standard sources. Falls back to archive resolution
    if all listed sources fail.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import GitRepository
        >>> settings = Settings()
        >>> repo = GitRepository()
        >>> version, ref = resolve_sources(["tag", "file"], settings, repo)

    :param sources: Priority list of source types to query.
    :param settings: Configuration settings instance.
    :param repository: Git repository wrapper.
    :return: Resolved version and associated Git reference.
    :raises VersionResolutionError: If no version is found in any source.
    """
    try:
        version, reference = resolve_from_explicit_source(settings, repository)
        logger.info(f"Resolved version from explicit config/argument: {version}")
        return version, reference
    except VersionResolutionError as exp_err:
        logger.info(
            f"Could not resolve version from explicit config/argument: {exp_err}"
        )

    if "auto" in sources:
        sources = ["file", "function", "tag", "branch", "commit"]
        logger.debug(f"Expanded 'auto' source type to: {sources}")

    logger.info(f"Resolving version sources in order: {sources}")

    resolvers = {
        "file": resolve_from_file_source,
        "function": resolve_from_function_source,
        "tag": functools.partial(resolve_from_git_source, "tag"),
        "branch": functools.partial(resolve_from_git_source, "branch"),
        "commit": functools.partial(resolve_from_git_source, "commit"),
    }

    for source in sources:
        resolver = resolvers.get(source)
        if not resolver:
            raise ValueError(f"Unknown source type: {source}")

        try:
            version, reference = resolver(settings, repository)
            if version:
                logger.info(
                    f"Successfully resolved version from source '{source}': {version}"
                )
                return version, reference
        except VersionResolutionError as ver_err:
            logger.info(f"Could not resolve version from source '{source}': {ver_err}")

    logger.info(
        "No version could be resolved from the configured sources, "
        "attempting to resolve from archive."
    )
    try:
        version, reference = resolve_sources_from_archive(sources, settings)
        logger.info(f"Resolved version from archive: {version} for {reference}")
        return version, reference
    except VersionResolutionError as archive_err:
        logger.info(f"Could not resolve version from archive: {archive_err}")

    raise VersionResolutionError(f"No version found for any of the sources: {sources}")


@autolog(exception_log_level="INFO")
def resolve_from_explicit_source(
    settings: Settings, repository: GitRepository
) -> tuple[Version, GitReference]:
    """Resolve version from an explicit configuration version string.

    Extracts a static version from settings, checking it against configured
    regex patterns. Rejects dynamic aliases like 'auto', 'dynamic', or '0.0.0'.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import GitRepository
        >>> settings = Settings(version="1.2.3")
        >>> repo = GitRepository()
        >>> version, ref = resolve_from_explicit_source(settings, repo)

    :param settings: Configuration settings containing the version string.
    :param repository: Target Git repository.
    :return: Resolved version and the current Git commit or fallback reference.
    :raises VersionResolutionError: If the version is unset or is a dynamic alias.
    """
    version_str = str(settings.version).strip().lower()
    if version_str in ("auto", "dynamic", "0.0.0", ""):
        raise VersionResolutionError(
            f"Explicit version is not set; value "
            f"'{version_str}' is not a valid version."
        )
    version = _extract_versions(list(settings.regex_version), version_str)[0]
    logger.info(f"Resolved explicit version: {version}")

    return version, repository.current_commit_or_fallback


@autolog(exception_log_level="INFO")
def resolve_from_file_source(
    settings: Settings, repository: GitRepository
) -> tuple[Version, GitReference]:
    """Resolve version by parsing a configured version source file.

    Reads the path specified in settings, matches its contents against file regex
    patterns, and extracts the first matching version.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import GitRepository
        >>> settings = Settings(version_source_file="setup.cfg")
        >>> repo = GitRepository()
        >>> version, ref = resolve_from_file_source(settings, repo)

    :param settings: Configuration settings specifying file path and regex.
    :param repository: Target Git repository.
    :return: Resolved version and current Git commit or fallback reference.
    :raises VersionResolutionError: If the file is missing, unreadable, or unmatched.
    """
    if not settings.version_source_file:
        logger.debug("No version_source_file configured.")
        raise VersionResolutionError("No version_source_file configured.")

    source_path = settings.resolve_path_from_root(settings.version_source_file)
    if not source_path or not source_path.exists():
        raise VersionResolutionError(f"Version file '{source_path}' not found.")

    logger.debug(f"Attempting to resolve version from file: {source_path}")

    try:
        content = source_path.read_text(encoding="utf-8")
    except OSError as read_err:
        raise VersionResolutionError(
            f"Failed to read version file '{source_path}': {read_err}"
        ) from read_err

    version = _extract_versions(list(settings.regex_file), content)[0]
    logger.info(
        f"Resolved version from file '{source_path}' using pattern "
        f"{settings.regex_file}: {version}"
    )
    return version, repository.current_commit_or_fallback


@autolog(exception_log_level="INFO")
def resolve_from_function_source(
    settings: Settings, repository: GitRepository
) -> tuple[Version, GitReference]:
    """Resolve version by executing a custom Python function.

    Dynamically imports and calls a user-defined function in the format
    'module:function', passing settings and repository as keyword arguments.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import GitRepository
        >>> settings = Settings(version_source_function="my_module:get_version")
        >>> repo = GitRepository()
        >>> version, ref = resolve_from_function_source(settings, repo)

    :param settings: Configuration settings containing the function path.
    :param repository: Target Git repository.
    :return: Reconciled version and associated Git reference returned by the function.
    :raises VersionResolutionError: If the function path is invalid or execution fails.
    :raises ValueError: If the function returns an invalid version or reference type.
    """
    if not settings.version_source_function:
        logger.debug("No version_source_function configured.")
        raise VersionResolutionError("No version_source_function configured.")

    if ":" not in str(settings.version_source_function):
        raise VersionResolutionError(
            f"Invalid function format: '{settings.version_source_function}'. "
            "Must be in format 'module:function'."
        )

    logger.debug(
        f"Attempting to resolve version from function: "
        f"{settings.version_source_function}"
    )

    # Insert into PATH to allow general importing within the package
    added_paths = []
    for path in [str(settings.project_root), str(settings.src_root)]:
        if path not in sys.path:
            sys.path.insert(0, path)
            added_paths.append(path)

    try:
        module_name, function_name = str(settings.version_source_function).split(":", 1)
        module = importlib.import_module(module_name)
        version, reference = getattr(module, function_name)(
            settings=settings, repo=repository
        )
        if not version or not isinstance(version, Version):
            raise ValueError(
                f"Version function '{settings.version_source_function}' did not "
                f"return a valid version. Got: {version}"
            )
        if not reference or not isinstance(reference, GitReference):
            raise ValueError(
                f"Version function '{settings.version_source_function}' did not "
                f"return a valid reference. Got: {reference}"
            )
        logger.info(
            f"Resolved version and reference from function "
            f"{settings.version_source_function}: {version} ({reference})"
        )
        return version, reference
    except Exception as error:
        logger.exception(
            f"Version function '{settings.version_source_function}' failed: {error}"
        )
        raise
    finally:
        for path in added_paths:
            if path in sys.path:
                sys.path.remove(path)


@autolog(exception_log_level="INFO")
def resolve_from_git_source(
    type_: Literal["tag", "branch", "commit"],
    settings: Settings,
    repository: GitRepository,
) -> tuple[Version, GitReference]:
    """Resolve version from Git metadata (tags, branch name, or commit messages).

    Extracts version candidates from the specified Git metadata type using regex
    patterns and selects the match closest to the HEAD commit.

    Example:
        >>> from gitversioned.settings import Settings
        >>> from gitversioned.utils import GitRepository
        >>> settings = Settings()
        >>> repo = GitRepository()
        >>> version, ref = resolve_from_git_source("tag", settings, repo)

    :param type_: Git metadata category to query ('tag', 'branch', or 'commit').
    :param settings: Configuration settings containing the regex patterns.
    :param repository: Target Git repository.
    :return: Resolved version and the closest matching Git reference.
    :raises VersionResolutionError: If Git is unavailable or no patterns match.
    :raises ValueError: If the metadata type is invalid.
    """
    if not repository.is_available:
        raise VersionResolutionError("No git repository available.")

    candidates: list[tuple[str, GitReference]] = []
    patterns: list[str] = []

    if type_ == "tag":
        patterns = list(settings.regex_tag)
        candidates = [(tag.tag_name, tag) for tag in repository.tags]
    elif type_ == "branch":
        patterns = list(settings.regex_branch)
        if repository.current_branch:
            candidates = [
                (
                    repository.current_branch.branch_name,
                    repository.current_branch,
                )
            ]
    elif type_ == "commit":
        patterns = list(settings.regex_commit)
        candidates = [(commit.commit_message, commit) for commit in repository.commits]
    else:
        raise ValueError(f"Invalid git type: {type_}")

    matches: list[tuple[Version, GitReference]] = []
    for text, reference in candidates:
        try:
            version = _extract_versions(patterns, text)[0]
            matches.append((version, reference))
        except VersionResolutionError as ver_err:
            logger.warning(
                f"Could not extract version from git {type_} '{text}' using "
                f"patterns {patterns}: {ver_err}"
            )
            continue

    if not matches:
        raise VersionResolutionError(
            f"No version found for git {type_} using patterns {patterns}"
        )

    logger.debug(f"Found {len(matches)} matches for git {type_}.")
    best_match = min(
        matches,
        key=lambda item: item[1].distance_from_head,
    )
    logger.info(
        f"Resolved version from git {type_}; "
        f"version={best_match[0]}, ref={best_match[1]}"
    )
    return best_match


@autolog(exception_log_level="INFO")
def resolve_sources_from_archive(
    sources: list[str], settings: Settings
) -> tuple[Version, GitReference]:
    """Resolve version from a git-archive export description file.

    Parses the archival export file, extracts Git metadata (tags, branches, commits)
    via regex, reconstructs a GitReference, and extracts versions matching the
    specified source types.

    Example:
        >>> from gitversioned.settings import Settings
        >>> settings = Settings(version_source_archive=".git_archival.txt")
        >>> version, ref = resolve_sources_from_archive(["tag"], settings)

    :param sources: Source types to query from the reconstructed Git metadata.
    :param settings: Configuration settings.
    :return: Resolved version and reconstructed Git reference.
    :raises VersionResolutionError: If the archive file is missing, raw, or unmatched.
    """
    archive_path = settings.resolve_path_from_root(settings.version_source_archive)
    if not archive_path or not archive_path.exists():
        raise VersionResolutionError(
            f"Version file not found for source {settings.version_source_archive}"
        )

    logger.debug(f"Attempting to resolve version from archive: {archive_path}")
    try:
        content = archive_path.read_text(encoding="utf-8")
    except OSError as read_err:
        raise VersionResolutionError(
            f"Failed to read archive file '{archive_path}': {read_err}"
        ) from read_err

    if not content or "$Format" in content:
        raise VersionResolutionError(
            "Archive file has not been formatted with 'git archive' or similar: "
            f"{content}"
        )

    ref_kwargs: dict[str, Any] = {}
    for pattern in settings.regex_archive:
        for match in re_module.finditer(pattern, content):
            ref_kwargs.update(
                {
                    key: value
                    for key, value in match.groupdict().items()
                    if value and key not in {"major", "minor", "micro", "patch", "bug"}
                }
            )

    reference = GitReference.model_validate(ref_kwargs)
    dispatch = {
        "tag": (reference.tag_name, list(settings.regex_tag)),
        "branch": (reference.branch_name, list(settings.regex_branch)),
        "commit": (reference.commit_message, list(settings.regex_commit)),
    }

    versions: list[Version] = []
    for source in sources:
        attr_val, pattern = dispatch.get(source, ("", []))
        if attr_val and pattern:
            try:
                versions = _extract_versions(pattern, attr_val)
                break
            except ValueError:
                logger.warning(f"Could not extract version from {source}: {reference}")

    if not versions:
        raise VersionResolutionError(
            f"No version found for archive '{archive_path}' using patterns: "
            f"{settings.regex_archive} and sources {sources}"
        )

    version = max(versions)
    logger.info(f"Resolved version from archive; version={version}, ref={reference}")
    return version, reference


# Helper to extract version objects matching regex patterns from input text.
def _extract_versions(patterns: list[str], text: str) -> list[Version]:
    versions: list[Version] = []
    for pattern in patterns:
        for match in re_module.finditer(str(pattern), text):
            groups = match.groupdict()
            major = groups.get("major")
            minor = groups.get("minor")
            micro = groups.get("micro") or groups.get("patch") or groups.get("bug")

            if major is None or minor is None or micro is None:
                logger.warning(
                    f"Invalid version string found for pattern {pattern} and "
                    f"text {text} with groups {groups}"
                )
                continue

            versions.append(Version(f"{major}.{minor}.{micro}"))

    if not versions:
        raise VersionResolutionError(
            f"No version found for patterns {patterns} and text {text}"
        )

    return versions
```

## File: src/gitversioned/compat.py

```python
"""
Compatibility abstractions for optional dependencies.

This module centralizes fallback logic for safely importing optional dependencies
like ``psutil``, ``opentelemetry``, and TOML parsers. It provides standardized
access points for these modules, avoiding scattered ``try-except`` blocks across
the codebase. Maintainers should import optional dependencies from this module
rather than attempting direct imports elsewhere.
"""

from __future__ import annotations

import importlib
import types
from typing import Annotated

__all__ = ["opentelemetry_trace", "psutil", "tomllib"]

_tomllib: types.ModuleType | None = None
try:
    _tomllib = importlib.import_module("tomllib")
except ImportError:
    try:
        _tomllib = importlib.import_module("tomli")
    except ImportError:
        _tomllib = None

_opentelemetry_trace: types.ModuleType | None = None
try:
    from opentelemetry import trace

    _opentelemetry_trace = trace
except ImportError:
    _opentelemetry_trace = None

_psutil: types.ModuleType | None = None
try:
    _psutil = importlib.import_module("psutil")
except ImportError:
    _psutil = None

opentelemetry_trace: Annotated[
    types.ModuleType | None,
    "Enables distributed tracing integration. Used for tracing execution paths "
    "when OpenTelemetry is present. Provides the ``opentelemetry.trace`` module "
    "or ``None``.",
] = _opentelemetry_trace

psutil: Annotated[
    types.ModuleType | None,
    "Enables retrieval of detailed system and process information. Used to monitor "
    "system context. Provides the ``psutil`` module or ``None``.",
] = _psutil

tomllib: Annotated[
    types.ModuleType | None,
    "Enables TOML file parsing. Used for reading configuration files like "
    "``pyproject.toml``. Provides the standard library ``tomllib``, the "
    "third-party ``tomli``, or ``None``.",
] = _tomllib
```

# SECTION 4: DOCUMENTATION BLOCKS

## File: README.md

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/logo-dark.svg">
    <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/logo-light.svg">
    <img alt="GitVersioned Logo" src="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/logo-light.svg" width="400">
  </picture>
</p>

<p align="center">
  <em>Simple, predictable, and opinionated versioning for Python projects.</em>
</p>

<p align="center">
  <!-- Package & Release Status -->
  <a href="https://github.com/markurtz/git-versioned/releases">
    <img src="https://img.shields.io/github/v/release/markurtz/git-versioned?label=Release" alt="GitHub Release">
  </a>
  <a href="https://pypi.org/project/gitversioned/">
    <img src="https://img.shields.io/pypi/v/gitversioned?label=PyPI" alt="PyPI Release">
  </a>
  <a href="https://pypi.org/project/gitversioned/">
    <img src="https://img.shields.io/pypi/pyversions/gitversioned?label=Python" alt="Supported Python Versions">
  </a>
  <br/>
  <!-- CI/CD & Build Status -->
  <a href="https://github.com/markurtz/git-versioned/actions/workflows/main.yml">
    <img src="https://github.com/markurtz/git-versioned/actions/workflows/main.yml/badge.svg" alt="CI Status">
  </a>
  <br/>
  <!-- Issues & Support -->
  <a href="https://github.com/markurtz/git-versioned/issues?q=is%3Aissue+is%3Aopen">
    <img src="https://img.shields.io/github/issues/markurtz/git-versioned?label=Issues%20Open" alt="Open Issues">
  </a>

<a href="https://opensource.org/licenses/Apache-2.0">
    <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
  </a>
</p>

<p align="center">
  <a href="https://markurtz.github.io/git-versioned">Documentation</a> |
  <a href="https://github.com/markurtz/git-versioned/milestones">Roadmap</a> |
  <a href="https://github.com/markurtz/git-versioned/issues">Issues</a> |
  <a href="https://github.com/markurtz/git-versioned/discussions">Discussions</a>
</p>

______________________________________________________________________

## Overview

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/user-flow-dark.svg">
    <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/user-flow-light.svg">
    <img alt="User Flow Diagram" src="https://raw.githubusercontent.com/markurtz/git-versioned/main/docs/assets/branding/user-flow-light.svg" width="800">
  </picture>
</p>

GitVersioned is a PEP 440-compliant Python versioning tool for Git repositories. It leverages Git history and CI environments as the ultimate source of truth to provide predictable, automated release versioning with zero runtime dependencies.

### Why GitVersioned?

GitVersioned combines strict PEP 440 compliance with extreme customizability, designed to drop cleanly into modern Python build systems.

- **Ironclad Auditability & Metadata:** Generates rich `version.py` files packed with Git hashes, branch names, and build context to ensure every artifact is fully traceable.
- **Predictability & Authority:** Enforces CI-driven and user-defined authority. Supports dynamic version resolution from tags, branches, commits, local files, archives (e.g., GitHub ZIP downloads), or custom Python hooks.
- **Native Integrations & CI/CD Ready:** Offers out-of-the-box support for **Hatch** and **Setuptools** and plugs effortlessly into modern CI pipelines (e.g., GitHub Actions) without tangled configurations.
- **Deep Customizability:** Features 25+ configuration settings via `pyproject.toml`, `setup.cfg`, or environment variables. Provides advanced ExStr templates and custom format strings for full control over metadata generation and release formatting.
- **Archive Fallback:** Seamlessly resolves version data from GitHub ZIP downloads or source archives when the `.git` directory is missing.
- **Submodule Awareness:** Intelligently handles versioning across complex Git submodule structures.

### Ecosystem Comparison

| Tool               | Versioning Logic & PEP 440                                                                               | Sourcing & Configuration                                                                           | Auditability & Metadata                                                                             | Integrations & DX                                                                          |
| ------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **`GitVersioned`** | **Predictable & Robust.** Enforces strict PEP 440 compliance with flow-based auto-incrementing logic.    | **Omni-Source.** Dynamically resolves from Git, archives, env vars, files, or custom Python hooks. | **Unmatched.** Generates rich `version.py` files packed with Git, build, and host environment data. | **Superior.** 2-line setup, zero runtime deps, and native Hatch/Setuptools support.        |
| `setuptools-scm`   | Heuristic. Relies on "guess-next-version" logic which can lead to unpredictable bumps.                   | VCS-First. Primarily Git/Hg tags; environment overrides are manual and difficult to manage.        | Minimal. Outputs a simple version string with basic stdout build logging.                           | Standard. Industry default but requires build-time dependencies and is tied to Setuptools. |
| `versioneer`       | Rigid. Uses basic tag-plus-distance logic with little room for customization.                            | VCS-Only. Hardcoded to read from repository tags and hashes.                                       | None. Injects a hardcoded Python module; executes silently with no tracing.                         | Legacy. Requires vendoring ~2,000 lines of code directly into your repository.             |
| `versioningit`     | Manual. Highly configurable but places the burden of PEP 440 compliance on complex user regex/templates. | Modular. Supports many sources but requires extensive individual manual configuration.             | Moderate. Customizable output via templates but lacks a unified, structured metadata model.         | Complex. Steep learning curve and plugin-heavy architecture.                               |
| `hatch-vcs`        | Basic. Inherits the standard "guess" logic from the broader SCM ecosystem.                               | Optimized. Tied tightly to Hatchling profiles and its specific environment.                        | Internal. Logging and metadata are strictly restricted to the Hatch ecosystem.                      | Niche. High ease of use for Hatch users, but inapplicable for other build backends.        |

## Quick Start

### Installation & Configuration

GitVersioned is primarily used as a build plugin. The preferred pathway is to configure it in your `pyproject.toml`:

```toml
[build-system]
requires = ["hatchling", "gitversioned"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "gitversioned"
```

To ensure `gitversioned` can resolve the version when users download your repository as a ZIP file (e.g., from GitHub) where the `.git` directory is missing, configure **Archive Support**:

1. Create a `.git_archival.txt` file in your repository root with the following format variables:
   ```text
   commit_sha: $Format:%H$
   short_sha: $Format:%h$
   timestamp: $Format:%aI$
   author_name: $Format:%an$
   author_email: $Format:%ae$
   ref_names: $Format:%D$
   commit_message:
   $Format:%B$
   ```
1. Enable variable substitution during archive creation by adding the following to your `.gitattributes` file:
   ```text
   .git_archival.txt export-subst
   ```

For full installation options, Setuptools alternatives, and step-by-step onboarding, see the **[Getting Started guide](docs/getting-started/index.md)**.

## Core Concepts

GitVersioned is built using modern Python tooling, enforcing strict code quality standards with Ruff and Mypy, and providing a robust Pydantic-driven settings architecture for configuration resolution.

### Component Architecture

The repository is structured to separate documentation, application logic, and testing cleanly:

- `src/gitversioned/`: The primary application source code. Contains core logic for Git interaction, version resolution, and template generation.
  - `plugins/`: Native integrations for build backends like Hatchling (`hatchling_plugin.py`) and Setuptools (`setuptools_plugin.py`).
- `tests/`: Comprehensive test suite ensuring reliability, organized into `unit/`, `integration/`, and `e2e/`.
- `docs/`: Source code for the MkDocs Material documentation site, including step-by-step guides, references, and getting started tutorials.
- `examples/`: Runnable reference projects demonstrating real-world configurations across various build systems and workflows.
- `.github/workflows/`: Advanced CI/CD pipelines governing the project lifecycle, built around reusable workflow templates.

## Advanced Usage

Please check the [`examples/`](https://github.com/markurtz/git-versioned/tree/main/examples/) directory for advanced examples and configurations.

## General

### Contributing

We welcome contributions! Please see our [Contributing Guide](https://github.com/markurtz/git-versioned/blob/main/CONTRIBUTING.md) for more details. For development setup, check out [DEVELOPING.md](https://github.com/markurtz/git-versioned/blob/main/DEVELOPING.md).
Please ensure you follow our [Code of Conduct](https://github.com/markurtz/git-versioned/blob/main/CODE_OF_CONDUCT.md) in all interactions.

### Support and Security

- For help and general questions, see [SUPPORT.md](https://github.com/markurtz/git-versioned/blob/main/SUPPORT.md).
- To report a security vulnerability, please refer to our [Security Policy](https://github.com/markurtz/git-versioned/blob/main/SECURITY.md).

### AI & LLM Tooling

This repository includes first-class support for agentic and LLM-assisted development workflows:

- **[AGENTS.md](https://github.com/markurtz/git-versioned/blob/main/AGENTS.md):** Repository-specific instructions for AI coding agents (Codex, Copilot Workspace, Gemini, Claude, Cursor, and similar tools). Contains the authoritative guide for project structure, executable commands, code style, and critical constraints.
- **[llms.txt](https://github.com/markurtz/git-versioned/blob/main/llms.txt):** A machine-readable index of the project's documentation, following the [llms.txt specification](https://llmstxt.org/). Served at `/llms.txt` on the documentation site to help LLMs quickly locate and consume relevant content.

### License

This project is licensed under the Apache License 2.0. See the [LICENSE](https://github.com/markurtz/git-versioned/blob/main/LICENSE) file for details.

### Citations

If you use this software in your research, please cite it using the following BibTeX entry:

```bibtex
@software{gitversioned,
  author = {Mark Kurtz},
  title = {gitversioned},
  year = {2026},
  url = {https://github.com/markurtz/git-versioned}
}
```

## File: AGENTS.md

# AGENTS.md — AI Agent & Coding Assistant Guide

This file provides repository-specific context, setup instructions, executable commands, and security boundaries for AI coding assistants.

## System Overview

`gitversioned` is an opinionated PEP 440-compliant Python versioning tool for Git repositories and submodules. It enforces CI and user authority over versioning, and generates structured `version.py` files with deep metadata for full auditability.

- **Primary Language:** Python 3.10+
- **Configuration & Build Backend:** Hatch (using `hatchling.build`)
- **Key Dependencies:** Pydantic / Pydantic-Settings v2, Loguru, Typer, Packaging, Setuptools

## Core Directories & Architecture

- `src/gitversioned/`: Core Python library.
  - `__main__.py`: CLI entrypoint (subcommands: `calculate`, `format`, `write`).
  - `settings.py`: Pydantic settings schema for project options.
  - `plugins/`: Integration modules for Hatchling and Setuptools.
  - `utils/`: Git, environment, and Pydantic validation helpers.
  - `versioning/`: Version calculation, template rendering, and output file writers.
- `tests/`: Organized into `python/unit/` (isolated logic), `python/integration/` (subsystem interactions), and `e2e/` (installed package & CLI integration).
- `docs/`: MkDocs Material documentation source.
- `.github/workflows/`: CI/CD workflows (prefixed with `pipeline-` and `util-`).

## Environment & Developer Workflows

This project is configured to run using Hatch environments. Use the local `.venv` for all executions as instructed by the user.

### 1. Setup & Bootstrapping

Activate the environment and initialize Hatch:

```bash
# Set up/update dependencies via Hatch inside virtualenv wrapper
.venv/bin/hatch env create
```

### 2. Testing Pipeline

Tests are tiered. Run targeted tests or full suite:

```bash
# Run all functional tests (unit + integration)
.venv/bin/hatch run python:tests-func

# Run unit tests only
.venv/bin/hatch run python:tests-unit
# Alternatively, via pytest directly:
.venv/bin/pytest tests/python/unit

# Run integration tests only
.venv/bin/hatch run python:tests-int

# Run E2E tests (builds dist wheel and installs it first)
.venv/bin/hatch run python:tests-e2e

# Run all tests with coverage reports
.venv/bin/hatch run tests-cov
```

### 3. Code Quality, Formatting & Types

Run formatting and quality gates before committing:

```bash
# Auto-format Python and project files
.venv/bin/hatch run python:format
.venv/bin/hatch run project:format

# Run all lint checks (Ruff, mdformat, yamlfix, taplo)
.venv/bin/hatch run python:lint
.venv/bin/hatch run project:lint

# Run static type checks (Mypy via Ty)
.venv/bin/hatch run python:types

# Run pre-commit hooks manually on all files
.venv/bin/pre-commit run --all-files
```

### 4. Documentation & Packaging

```bash
# Build and serve docs locally (http://127.0.0.1:8000)
.venv/bin/hatch run project:docs-serve

# Build package distributions (sdist and wheel)
.venv/bin/hatch build
```

## Security & Behavior Boundaries

To maintain project integrity and security, agents must strictly adhere to the following rules:

### 1. Secrets & Credentials

- **Never commit secrets:** Never add API keys, tokens, or credentials anywhere.
- Run security audits using: `.venv/bin/hatch run project:security`.

### 2. Critical Files & CI Guardrails

- **Do not modify `LICENSE` or `NOTICE`.**
- **Do not modify GitHub Actions workflow triggers** (in `.github/workflows/`) without explicit human review.
- **Apache 2.0 copyright header:** Every new Python source file must begin with the standard Apache 2.0 copyright and license notice.

### 3. Execution Constraints

- Always use tools installed in the `.venv` (e.g. `.venv/bin/hatch`, `.venv/bin/pytest`).
- Avoid global packages or running unverified external binaries.
- Do not add new external dependencies to `pyproject.toml` without verifying compatibility with Python 3.10+.

## File: CLAUDE.md

Refer to [AGENTS.md](AGENTS.md) for build, testing, quality controls, and agent instructions.

## File: llms.txt

# GitVersioned

> Opinionated PEP 440 Python versioning for Git repos and submodules.

`gitversioned` enforces CI and user authority over versioning, and generates rich `version.py` files with deep metadata for full auditability.
For complete, merged context containing the full codebase and all guides, see [llms-full.txt](llms-full.txt).

## Primary Documentation Links
- [Documentation Home](https://markurtz.github.io/git-versioned/): Main site home page
- [Getting Started](https://markurtz.github.io/git-versioned/getting-started/): Onboarding, installation, and setup
- [Concepts & Architecture](https://markurtz.github.io/git-versioned/guides/concepts/): PEP 440, dirty state checks, and metadata fields
- [Configuration Guide](https://markurtz.github.io/git-versioned/guides/configuration/): Setting overrides via `pyproject.toml`
- [Integration: Hatchling](https://markurtz.github.io/git-versioned/guides/hatchling/): Hatch versioning plugin integration
- [Integration: Setuptools](https://markurtz.github.io/git-versioned/guides/setuptools/): Setuptools versioning hook integration
- [API Reference Documentation](https://markurtz.github.io/git-versioned/reference/): Complete API reference

## Architectural Topography

### Configuration & Entrypoints
- [pyproject.toml](pyproject.toml): Project settings, environment scripts, and PEP 518/621 metadata.
- [AGENTS.md](AGENTS.md): Foundational agent development guidelines, build commands, and security boundaries.
- [CLAUDE.md](CLAUDE.md): Direct pointer routing assistants to `AGENTS.md`.
- [src/gitversioned/__main__.py](src/gitversioned/__main__.py): Standalone CLI entrypoint. Exposes subcommands:
  - `calculate`: Computes and outputs the PEP 440 version string only.
  - `format`: Formats and prints rendered template outputs to stdout.
  - `write`: Resolves version and writes it directly to the designated output file (e.g. `version.py`).

### Core Library Modules
- [src/gitversioned/settings.py](src/gitversioned/settings.py): Pydantic schemas validating configuration fields.
- [src/gitversioned/logging.py](src/gitversioned/logging.py): Loguru logging setup and stdout/stderr separation.
- [src/gitversioned/plugins/hatchling_plugin.py](src/gitversioned/plugins/hatchling_plugin.py): Hook for Hatch builds.
- [src/gitversioned/plugins/setuptools_plugin.py](src/gitversioned/plugins/setuptools_plugin.py): Hook for Setuptools builds.
- [src/gitversioned/utils/git.py](src/gitversioned/utils/git.py): Git repository information extraction via subprocess hooks.
- [src/gitversioned/utils/environment.py](src/gitversioned/utils/environment.py): Target environment configuration resolver.
- [src/gitversioned/versioning/entrypoints.py](src/gitversioned/versioning/entrypoints.py): Package endpoints for resolving and writing versions.
- [src/gitversioned/versioning/generation.py](src/gitversioned/versioning/generation.py): Version file template compilation and formatting.
- [src/gitversioned/versioning/sources.py](src/gitversioned/versioning/sources.py): Git state and environment variable version resolvers.

### Testing Suite Tiers
- [tests/python/unit/](tests/python/unit/): Fully isolated unit tests for individual functions and classes.
- [tests/python/integration/](tests/python/integration/): Component integration checks.
- [tests/e2e/](tests/e2e/): High-level integration tests executing CLI flows and package builds.

## File: DEVELOPING.md

# Developing `gitversioned`

This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards.

## Setup & Prerequisites

Ensure your system meets the requirements below to establish a consistent local development environment, or utilize our containerized development setup.

### Supported Operating Systems

- **macOS & Linux**: Standard operating systems that are fully supported, actively tested, and maintained.
- **Windows**: Not officially tested or maintained. Windows users encountering issues should use the [Development Environment Container](#development-environment-container-devcontainer) setup.

### Development Environment Container (.devcontainer)

- **Requirements**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [VS Code](https://code.visualstudio.com/) with the **[Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers)** extension installed.
- **Usage**:
  1. Clone this repository: `git clone https://github.com/markurtz/git-versioned.git`
  1. Open the project folder in VS Code.
  1. A prompt will appear: "Reopen in Container". Click it to launch the environment.
  1. VS Code will build the container and automatically run `uv sync --all-groups --all-extras` to install and sync the Python environment.

> [!NOTE]
> **Local `.venv` vs. Hatch Environments**:
> The `uv sync` command creates a local `.venv` in the project root solely to provide VS Code extensions (like [Pylance](https://github.com/microsoft/pylance-release) and [Ruff](https://astral.sh/ruff)) with a standard environment for editor autocomplete, hover information, and in-editor diagnostics. All command-line and automated task execution (formatting, linting, testing, building) is managed via **[Hatch](https://hatch.pypa.io/)** isolated environments (`hatch run ...`). Do not activate or modify this root `.venv` directly for running tasks.

### Local Setup

- **[Git](https://git-scm.com/)**: Version control tool. Refer to the [Git Documentation](https://git-scm.com/doc) for installation instructions.
- **[Docker](https://www.docker.com/)**: Container management system. Install via the [Docker Installation Guide](https://docs.docker.com/get-docker/).
- **[Python](https://www.python.org/) 3.10 - 3.14**: Core runtime environment. Install via the [Python Downloads Page](https://www.python.org/downloads/).
- **[uv](https://docs.astral.sh/uv/)**: Fast package installer and resolver. Install via the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
- **[Hatch](https://hatch.pypa.io/)**: Project workflow orchestrator. Install via the [Hatch installation guide](https://hatch.pypa.io/latest/install/). If you have `uv` installed, we recommend installing Hatch cleanly as a tool using:
  ```bash
  uv tool install hatch
  ```
  to avoid polluting your global system packages.

> [!TIP]
> **Editor Autocomplete Setup (Local)**:
> For local development outside of the Dev Container, if you want your editor (VS Code, [PyCharm](https://www.jetbrains.com/pycharm/), etc.) to resolve imports and provide autocomplete/diagnostics, run `uv sync --all-groups --all-extras` once to create the local `.venv`.

## Developer Quickstart

Once your environment is set up (either via the Dev Container or manually), follow this consolidated workflow for a standard development cycle:

- **Branch & Code**: Create your feature branch and make changes:
  ```bash
  git checkout -b feat/my-contribution
  ```
- **Quality Assurance (Unified)**: Automatically format code, lint, type check, and run security scans across all environments:
  ```bash
  hatch run all:quality
  ```
  *(Alternatively, you can run individual checks if preferred: `hatch run all:format`, `hatch run all:lint`, `hatch run all:types`, or `hatch run all:security`)*
- **Test (Unified)**: Run all unit, integration, and E2E tests with coverage:
  ```bash
  hatch run all:tests-cov
  ```
  *(For running tests without coverage: `hatch run all:tests`)*
- **Build (Unified)**: Compile package artifacts (source & wheels) and build the OCI container image *(requires Docker daemon to be running for the OCI phase)*:
  ```bash
  hatch run all:build
  ```
  *(To build only the Python wheel locally: `hatch build`)*
- **Serve Documentation**: Serve documentation locally (this automatically builds the site):
  ```bash
  hatch run all:docs-serve
  ```
- **Push**: Push your changes to open a Pull Request:
  ```bash
  git push -u origin feat/my-contribution
  ```

## Hatch Development Environments Overview

Our build, verification, and execution pipelines are partitioned into target-specific environments using Hatch. This ensures isolation, prevents dependency bloat, and standardizes workflows:

- **`default`**: The base environment template. It configures shared environment variables (such as target paths, directory structures, and script file paths) and installs the core dependency groups.
- **`all`**: The orchestrator environment. It defines cascading workflows to run formatting, linting, typing, security scanning, testing, and documentation generation across all components sequentially or concurrently.
- **`python`**: Encompasses Python-specific verification tools including [Ruff](https://astral.sh/ruff) for linting/formatting, [Ty](https://github.com/astral-sh/ty) for type-checking, [Pytest](https://docs.pytest.org/) for testing, and [Typer](https://typer.tiangolo.com/) for CLI documentation generation.
- **`oci`**: Manages OCI container builds (`docker build`), compose verification (`docker compose config`), linting ([hadolint](https://github.com/hadolint/hadolint)), security auditing ([trivy](https://trivy.dev/), [dockle](https://github.com/goodwithtech/dockle)), and container structure tests ([cstest](https://github.com/GoogleContainerTools/container-structure-test)).
- **`project`**: Targets repository-wide configuration and file standards, including Markdown formatting (`[mdformat](https://github.com/executablebooks/mdformat)`), configuration checkouts (`[yamlfix](https://github.com/lyz-code/yamlfix)`, `[yamllint](https://github.com/adrienverge/yamllint)`, `[taplo](https://taplo.tamasfe.dev/)`), security baselines (`[detect-secrets](https://github.com/Yelp/detect-secrets)`, `[checkov](https://www.checkov.io/)`), link checkers, and site compilation (using the **[Zensical](https://zensical.org)** static site generator/documentation compiler).

## Coding Workflows

All development commands are unified under [pyproject.toml](./pyproject.toml) and managed using Hatch. The commands are generally invoked using the format:

```bash
hatch run [ENVIRONMENT]:[SCRIPT]
```

For orchestrating tasks across all environments, use the `all` environment scripts:

```bash
hatch run all:[SCRIPT]
```

### Quality Assurance & Static Analysis

This workflow enforces code quality, style conventions, static type correctness, and security policies across all codebase layers.

> [!TIP]
> **Unified Quality Check**:
> You can run all formatting, linting, type-checking, and security scans across all environments in a single command using the unified quality check:
>
> ```bash
> hatch run all:quality
> ```

| Environment            | Formatting Command         | Linting Command          | Type-Checking Command        | Security Auditing Command    |
| :--------------------- | :------------------------- | :----------------------- | :--------------------------- | :--------------------------- |
| **All / Orchestrator** | `hatch run all:format`     | `hatch run all:lint`     | `hatch run all:types`        | `hatch run all:security`     |
| **Python**             | `hatch run python:format`  | `hatch run python:lint`  | `hatch run python:types`     | `hatch run python:security`  |
| **OCI**                | `hatch run oci:format`     | `hatch run oci:lint`     | `hatch run oci:types` \*     | `hatch run oci:security`     |
| **Project**            | `hatch run project:format` | `hatch run project:lint` | `hatch run project:types` \* | `hatch run project:security` |

*\* Note: Type checking is not applicable for OCI and Project environments; executing these commands will output an information message.*

#### Code Formatting

- **Tools / Methodology / Rationale**:
  - **Python**: Uses `[ruff](https://astral.sh/ruff)` to automatically check/fix imports and format code layout. This delivers high-performance style standardization.
  - **OCI**: Uses `[dclint](https://github.com/zavoloklom/docker-compose-linter)` (via a helper script) to auto-format Docker Compose files. While `dclint` is primarily a compose linter, the format step (`hatch run oci:format`) executes it with the `--fix` flag to automatically correct lint errors and standard style issues in place. (Dockerfile linting/validation is handled separately by `[hadolint](https://github.com/hadolint/hadolint)`).
  - **Project**: Employs `[mdformat](https://github.com/executablebooks/mdformat)` for Markdown, `[yamlfix](https://github.com/lyz-code/yamlfix)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/)` for TOML file formatting to maintain a uniform structure for all configuration and documentation files.
- **Expected Outputs & Locations**:
  - In-place modifications applied directly to the files targeted by the respective environment variables: `PYTHON_TARGETS`, `MDFORMAT_TARGETS` (Markdown targets), `YAML_TARGETS`, and `TOML_TARGETS`.

#### Linting & Verification

- **Tools / Methodology / Rationale**:
  - **Python**: Runs `[ruff](https://astral.sh/ruff) check` and `[ruff](https://astral.sh/ruff) format --check` to verify compliance with PEP 8 and project style guidelines without modifying files.
  - **OCI**: Uses `[hadolint](https://github.com/hadolint/hadolint)` to validate Dockerfile syntax and standard practices, and runs `docker compose config` to verify the syntactic and semantic validity of compose files.
  - **Project**: Runs `[mdformat](https://github.com/executablebooks/mdformat) --check` to check Markdown formatting, `[yamlfix](https://github.com/lyz-code/yamlfix) --check` and `[yamllint](https://github.com/adrienverge/yamllint)` for YAML files, and `[taplo](https://taplo.tamasfe.dev/) check` for TOML configuration syntax.
- **Expected Outputs & Locations**:
  - Summary reports, warnings, and errors output directly to the terminal stdout/stderr. Standard exit codes (non-zero on failures) are used to gate CI pipelines.

#### Static Type Checking

- **Tools / Methodology / Rationale**:
  - **Python**: Employs Astral's `[ty check](https://github.com/astral-sh/ty)` frontend to statically analyze and verify Python type annotations.
- **Expected Outputs & Locations**:
  - Type checker error listings and tracebacks are printed to the terminal console.

#### Security & Vulnerability Auditing

- **Tools / Methodology / Rationale**:
  - **Python**: Employs `[semgrep](https://semgrep.dev/)` for semantic pattern matching, `[pip-audit](https://github.com/pypa/pip-audit)` to detect known vulnerabilities in Python packages, and `[ruff](https://astral.sh/ruff) check --select S` to check for security vulnerabilities.
  - **OCI**: Scans built containers using `[dockle](https://github.com/goodwithtech/dockle)` (verifies image best practices/secrets) and `[trivy](https://trivy.dev/)` (scans OS-level packages for CVEs).
  - **Project**: Employs `[detect-secrets](https://github.com/Yelp/detect-secrets)` to scan for accidentally committed secrets against a baseline, and `[checkov](https://www.checkov.io/)` to scan infrastructure-as-code files and development configurations.
- **Expected Outputs & Locations**:
  - Standard reports output to the console.
  - Project environment updates and validates the secrets baseline file located at `.detect-secrets.scan.json`. Run `hatch run project:security-update` to update this baseline file.

### Testing Strategy & Suites

Our testing strategy is split into component-level, integration-level, and system-level suites, each of which supports code coverage reporting.

#### Coverage Configurations & Directories

Code coverage runs collect data during test executions and format them into human-readable Markdown summaries.

- **Python Coverage**: Configured to output to `coverage/python/` (`PYTHON_COV_DIR`). The test suites automatically output terminal reports and compile Markdown reports (e.g., `coverage_tests-unit.md`).

| Test Suite                | Python Command                    | OCI Command                   | Project Command                   | All Command                    |
| :------------------------ | :-------------------------------- | :---------------------------- | :-------------------------------- | :----------------------------- |
| **All Local Tests**       | N/A                               | N/A                           | N/A                               | `hatch run all:tests`          |
| **All Tests + Coverage**  | N/A                               | N/A                           | N/A                               | `hatch run all:tests-cov`      |
| **Functional Tests**      | `hatch run python:tests-func`     | N/A                           | N/A                               | `hatch run all:tests-func`     |
| **Func Tests + Coverage** | `hatch run python:tests-func-cov` | N/A                           | N/A                               | `hatch run all:tests-func-cov` |
| **Unit Tests**            | `hatch run python:tests-unit`     | N/A                           | N/A                               | `hatch run all:tests-unit`     |
| **Unit Tests + Coverage** | `hatch run python:tests-unit-cov` | N/A                           | N/A                               | `hatch run all:tests-unit-cov` |
| **Integration Tests**     | `hatch run python:tests-int`      | N/A                           | `hatch run project:tests-int`     | `hatch run all:tests-int`      |
| **Int Tests + Coverage**  | `hatch run python:tests-int-cov`  | N/A                           | `hatch run project:tests-int-cov` | `hatch run all:tests-int-cov`  |
| **End-to-End Tests**      | `hatch run python:tests-e2e`      | `hatch run oci:tests-e2e`     | `hatch run project:tests-e2e`     | `hatch run all:tests-e2e`      |
| **E2E Tests + Coverage**  | `hatch run python:tests-e2e-cov`  | `hatch run oci:tests-e2e-cov` | `hatch run project:tests-e2e-cov` | `hatch run all:tests-e2e-cov`  |

*\* Note: While Hatch commands for `N/A` cells can technically be run (and will print a message stating that the test suite is not defined for that environment), they have no logical test targets or execution paths. They are marked `N/A` for clarity.*

#### Test Suites Breakdown

#### Full Suite (`tests` / `tests-cov`)

- **Methodology & Rationale**: Executes all local functional and E2E tests across all environments to ensure complete validation of the codebase before code integration.
- **Expected Outputs & Locations**: Unified console log output, combined test summaries, and all coverage Markdown files compiled under `coverage/python/`.

#### Functional Testing (`tests-func` / `tests-func-cov`)

- **Methodology & Rationale**: Executes both unit and integration tests under the targeted environment to verify logical flows and subsystem communication.
- **Expected Outputs & Locations**:
  - **Python**: Outputs to console and `coverage/python/coverage_tests-func.md`.

#### Unit Testing (`tests-unit` / `tests-unit-cov`)

- **Methodology & Rationale**:
  - **Python**: Runs isolated tests under `tests/python/unit` via `pytest`. Focuses on validating individual modules and class behaviors.
- **Expected Outputs & Locations**:
  - **Python**: Outputs `coverage/python/coverage_tests-unit.md`.

#### Integration Testing (`tests-int` / `tests-int-cov`)

- **Methodology & Rationale**:
  - **Python**: Runs tests under `tests/python/integration` via `pytest` to verify interactions between Python modules.
  - **Project**: Runs documentation code block tests. It utilizes `scripts/generate_doc_tests.py` to parse Markdown files and compile code block assertions under `.tests/docs` (`DOC_TESTS_PATH`), which are then executed using `pytest`.
- **Expected Outputs & Locations**:
  - **Python**: Outputs `coverage/python/coverage_tests-int.md`.
  - **Project**: Verifies doc tests compile and pass; outputs progress to stdout.

#### End-to-End Testing (`tests-e2e` / `tests-e2e-cov`)

- **Methodology & Rationale**:
  - **Python**: Compiles Python packages with `hatch build`, force reinstalls them via `pip`, and runs pytest against `tests/e2e` (`E2E_TESTS`) to verify CLI commands and package distribution paths in a black-box environment.
  - **OCI**: Builds the OCI image and executes Google's Container Structure Tests (`cstest` via `scripts/run_oci.py`) to confirm that the image metadata, file layouts, and execution endpoints conform to specifications.
  - **Project**: Executes `scripts/check_links.py` to recursively crawl project documents (`MDFORMAT_TARGETS`) and verify all internal/external links resolve successfully.
- **Expected Outputs & Locations**:
  - **Python**: Outputs `coverage/python/coverage_tests-e2e.md`.
  - **OCI**: Outputs Container Structure Test results to the console.
  - **Project**: Outputs link-checking validation summaries to the console.

#### Test Categorization & Test Pathways

To manage test execution speed and pipeline efficiency, every Python test is categorized into one of our three test pathways: **smoke**, **sanity**, or **regression**. These pathways directly govern how frequently and in which environments those tests are executed in CI/CD pipelines.

##### Pathway Specification & Filtering

A test's pathway can be specified and detected in one of two ways:

1. **By Marker**: Decorating the test function or class with a custom pytest marker (e.g., `@pytest.mark.smoke`, `@pytest.mark.sanity`, `@pytest.mark.regression`).
1. **By Name**: Including the pathway name in the test function or class name (e.g., `def test_smoke_initialization()`, `class TestSanityCore`, `def test_regression_bug_fix()`).

##### Test Pathways Breakdown

- **`smoke`**:
  - **Encapsulation & Scope**: Extremely fast, non-flaky, critical-path verification checks. These confirm that the fundamental, basic logic of the application functions correctly (e.g., orchestrator bootstrap, CLI command recognition).
  - **Execution Frequency**: Run on **every commit and Pull Request** (e.g., `development.yml`) as a quick health gate.
- **`sanity`**:
  - **Encapsulation & Scope**: Detailed, comprehensive tests of core system behaviors, APIs, and edge cases. These verify that the main business logic functions robustly but may take slightly longer than smoke tests.
  - **Execution Frequency**: Run on **pushes to the main branch** (e.g., `main.yml`) and release branches to ensure overall stability of the codebase.
- **`regression`**:
  - **Encapsulation & Scope**: Deep, system-wide, and heavy integration/E2E regression verification checks. These ensure that complex interactions, edge cases, and past bugs do not reappear.
  - **Execution Frequency**: Run on **nightly, weekly, and release schedule pipelines** due to their longer execution time.

##### Default Pathway Execution

If no specific filtering arguments, markers, or keyword flags are provided, pytest assumes a **regression** pathway by default.

Running the test suite without any arguments executes a full regression run. This is because a default regression run executes:

- All smoke tests
- All sanity tests
- All regression tests
- Any tests not marked or named under a specific category

##### Filtering Python Tests

Hatch dynamically passes CLI arguments through to the underlying `pytest` execution via the `{args}` placeholder configured in [pyproject.toml](./pyproject.toml). To filter by test pathways (`smoke`, `sanity`, or `regression`), use pytest's keyword option (`-k`). This correctly matches both annotated markers and naming patterns (e.g., pathway keywords in the function or class name).

- **Run only smoke tests**:
  ```bash
  hatch run python:tests-unit -k smoke
  ```
- **Run sanity and smoke tests**:
  ```bash
  hatch run python:tests-unit -k "sanity or smoke"
  ```

##### Testing a Specific Sub-Package or File

Hatch environments make it easy to target a specific test directory, sub-package, or single file by appending the path to your `hatch run` command. The provided path will override the default directories configured in `pyproject.toml`.

- **Run all tests in a specific file**:
  ```bash
  hatch run python:tests-unit tests/python/unit/test_settings.py
  ```
- **Run tests in a specific sub-package / directory**:
  ```bash
  hatch run python:tests-unit tests/python/unit/compat/
  ```
- **Run functional tests for a specific integration file**:
  ```bash
  hatch run python:tests-func tests/python/integration/test_utils.py
  ```

### Documentation Workflows

Our documentation is managed as code. It includes auto-generated CLI references and a unified project site built using **[Zensical](https://zensical.org)**.

> [!NOTE]
> **Zensical Documentation Tool**:
> [Zensical](https://zensical.org) is a static site generator and documentation compiler configured via `zensical.toml` that integrates [MkDocs](https://www.mkdocs.org/) and its plugin ecosystem (such as [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) and [macros](https://mkdocs-macros-plugin.readthedocs.io/)) under a simplified configuration structure.

#### CLI Documentation Generation

- **Tools / Methodology / Rationale**: Uses the `[typer](https://typer.tiangolo.com/)` utility to compile and output reference docs directly from the Python entrypoint `src/gitversioned/__main__.py`.
- **Command**: `hatch run python:docs`
- **Expected Outputs & Locations**: A generated Markdown reference file at `.docs/cli.md`.

#### Project Website Compilation

- **Tools / Methodology / Rationale**: Compiles the final developer documentation site via **Zensical**, incorporating the general Markdown guides and Python CLI docs.
- **Command**: `hatch run project:docs` (or `hatch run all:docs` to generate Python docs and compile project docs together)
- **Expected Outputs & Locations**: Static build files compiled to the `site/` directory.

> [!TIP]
> **Dynamic Coverage Report Inclusion**:
> When compiling the website locally, Zensical dynamically embeds the Python test coverage reports (extracted from `coverage/python/`) into the final reference page (`docs/reference/python_coverage.md`). If the coverage reports have been generated locally, they will automatically be included in the compiled docs site.

#### Live Development Preview Server

- **Tools / Methodology / Rationale**: Launches a hot-reloading web server to preview changes locally in real-time.
- **Command**:
  - Local Project Server: `hatch run project:docs-serve`
  - Global Orchestrator: `hatch run all:docs-serve`
- **Expected Outputs & Locations**: Hot-reloading site hosted locally at `http://localhost:8000`.

### Build & Distribution Workflows

These workflows handle compiling code and building containerized runtimes for distribution.

#### Python Package Build

- **Tools / Methodology / Rationale**: Uses [Hatchling](https://pypi.org/project/hatchling/) (configured under `[build-system]` in `pyproject.toml`) to bundle Python distribution wheel and source packages.
- **Command**: `hatch build`
- **Expected Outputs & Locations**: Built source distributions and `.whl` files output to the `dist/` directory.

#### OCI Container Image Build

- **Tools / Methodology / Rationale**: Executes a Docker build to compile the multi-stage production image, tagging the result using metadata parameters.
- **Command**: `hatch run oci:build`
- **Expected Outputs & Locations**: Local Docker image compiled and tagged as `gitversioned:latest` (configured via `{env:OCI_IMAGE}`).

## CI/CD Workflows

We maintain high quality gates using git workflows, automated reviews, and [GitHub Actions](https://github.com/features/actions) pipelines.

### Version Control Standards

- **Tools**: Git
- **Workflow & Commands**:
  - **Branching Model**: Standard branch prefixes are enforced:
    - Features: `feature/short-description`
    - Bugs: `bugfix/short-description`
    - Docs: `docs/short-description`
  - **Commit Messages**: Enforce [Conventional Commits](https://www.conventionalcommits.org/) (e.g. `feat: ...`, `fix: ...`, `docs: ...`).
  - **Versioning Tags**: Release tags must follow semver format (`v*.*.*`).

### Repository Policy & Pull Requests

- **Tools**: GitHub Pull Requests and Review tools
- **Workflow & Commands**:
  - Open a PR against the `main` branch.
  - All pipeline checks must pass (Linting, static typing, security gates, unit/integration/e2e tests).
  - Require review and approval from at least one core maintainer before merging.

### GitHub Actions Architecture

Our pipelines use a highly modular and DRY architecture to avoid duplication of setup steps:

- **Tools**: [GitHub Actions](https://github.com/features/actions)

- **Configuration / Manifest Files**: Reusable actions under `.github/actions/...` and triggers under `.github/workflows/...`

- **Python Versioning & Parameters**:

  - All composite actions (Python, OCI, and Project) support an optional `python-version` parameter.
  - If omitted, actions standardize on the oldest supported version (default: `"3.10"`).
  - All composite actions using change detection (`[dorny/paths-filter](https://github.com/dorny/paths-filter)`) support a `force-run` parameter (default: `"false"`). When set to `"true"`, it bypasses path-filtering check gates and executes the steps unconditionally (used in scheduled and release workflows).

- **OCI Tools Native Execution**:

  - The repository utilizes unified platform-agnostic OCI runner logic (`scripts/run_oci.py`).
  - When running in CI under `.github/actions/oci/`, the actions natively install OCI scanning and linting tools (`[hadolint](https://github.com/hadolint/hadolint)`, `[dclint](https://github.com/zavoloklom/docker-compose-linter)`, `[dockle](https://github.com/goodwithtech/dockle)`, `[trivy](https://trivy.dev/)`, and `[container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)`) on the runner.
  - This native pre-installation ensures that `run_oci.py` executes these binaries directly on the host machine, bypassing the performance overhead and Docker socket mounting requirements of containerized container-in-container execution.

  > [!WARNING]
  > **Tool Version Drift Risk**:
  > Running these tools natively in CI while developers run them locally via Docker fallback containers (e.g., `aquasec/trivy:latest`) can lead to version drift. To prevent the *"it passes locally but fails in CI"* issue:
  >
  > 1. Keep your system-installed binaries updated to match the versions used in CI workflows (defined in the OCI composite actions).
  > 1. Periodically pull the latest container images locally (`docker pull aquasec/trivy:latest`) to keep Docker fallbacks in sync with CI runner environments.

- **Utility Actions (`.github/actions/utility/...`)**:

  - `setup-python`: Sets up Python, and installs uv and Hatch.

- **Environment Actions (`.github/actions/[env]/...`)**:

  - Partitioned into folders for each environment: `python`, `oci`, and `project`.
  - Inside each environment, standard actions run specific scripts:
    - `quality`: Runs formatting, linting, and type checking.
    - `security`: Runs dependency audits, secrets checks, and security linters.
    - `tests`: Runs unit, integration, and E2E tests, accepting `test-level`, `test-category`, and `generate-coverage` inputs.
    - `build`: Compiles wheels (Python), container images (OCI), or all elements (Project).
    - `publish`: Publishes release packages to [PyPI](https://pypi.org/) (Python) or container images to [GHCR](https://github.com/features/packages) (OCI).

- **Workflows (`.github/workflows/...`)**: Triggered pipelines separated into:

  - **Core Pipelines**:
    - `pipeline-development.yml`: PR checks (quality, security, package build, tests, and documentation previews).
    - `pipeline-main.yml`: Triggered on push to `main` branch (runs full checks and deploys latest docs).
    - `pipeline-nightly.yml`: Nightly regression tests, vulnerability audits, and nightly releases.
    - `pipeline-release.yml`: Release tag pushes (`v*.*.*`); packages binary builds, attests them, publishes to PyPI and GHCR, and creates releases.
    - `pipeline-weekly.yml`: Scheduled weekly checks to verify environment health.
  - **Utility Workflows**:
    - `util-development-cleanup.yml`: Cleans up transient PR doc deployments.
    - `util-pr-comment.yml`: Securely posts PR comments (build status, compiled coverage summary, documentation previews, and build packages) to avoid fork permission limits.

### Local Workflow Testing with `act`

You can test and validate [GitHub Actions](https://github.com/features/actions) workflows locally on your development machine using [nektos/act](https://github.com/nektos/act). This ensures that workflows run correctly before you push changes to GitHub.

#### Prerequisites

1. Install **[Docker](https://www.docker.com/)** (required by `act` to spin up runner containers).
1. Install `act` using your package manager:
   - macOS ([Homebrew](https://brew.sh/)): `brew install act`
   - Linux (curl): `curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash`

> [!IMPORTANT]
> **Apple Silicon (M-series Chips) Emulation**:
> If you are on an Apple Silicon Mac, you must specify the target execution architecture using `--container-architecture linux/amd64`. This ensures that `act` pulls the `amd64` container image and installs pre-compiled `x86_64` wheels (such as `taplo`), bypassing compile-from-source errors due to missing arm64 wheels.

#### Running Workflows Locally

Run `act` from the repository root:

- **List all jobs**:

  ```bash
  act -l
  ```

- **Run the default (pull_request) event (runs Development Pipeline)**:

  ```bash
  act pull_request
  ```

- **Run a specific job (e.g., project-quality)**:

  ```bash
  act -j project-quality
  ```

- **Dry-run a workflow (displays steps without execution)**:

  ```bash
  act -n
  ```

#### Mocking Event Payloads (Change Detection)

Because composite actions use `dorny/paths-filter` to detect path-level changes, running `act` directly will fail if the required event metadata is missing. You can provide a mock payload (`event.json`) to simulate the GitHub event context:

1. Create an `event.json` in the root of the repository:
   ```json
   {
     "repository": {
       "default_branch": "main"
     }
   }
   ```
1. Pass the payload file using the `-e` flag:
   ```bash
   act push -W .github/workflows/pipeline-main.yml -j project-quality -e event.json --container-architecture linux/amd64
   ```

> [!NOTE]
> `act` runs steps inside Docker containers that simulate GitHub environments. By default, it uses a medium-sized Ubuntu image, but you can specify a fuller image using `act -P ubuntu-latest=catthehacker/ubuntu:act-latest`.

### Security & Code Scanning Gates

- **Tools**: [detect-secrets](https://github.com/Yelp/detect-secrets) (secret scanning), [checkov](https://www.checkov.io/) (infrastructure auditing), [semgrep](https://semgrep.dev/) (semantic scanning), [pip-audit](https://github.com/pypa/pip-audit) (Python package audits), and [trivy](https://trivy.dev/) / [dockle](https://github.com/goodwithtech/dockle) (OCI image scanning).
- **Workflow & Rationale**:
  - **Secret Gating**: [detect-secrets](https://github.com/Yelp/detect-secrets) runs locally and in PR gates against the committed `.detect-secrets.scan.json` baseline to prevent credential leaks.
  - **Static Analysis & CVE Auditing**: [Semgrep](https://semgrep.dev/), [Trivy](https://trivy.dev/), [Checkov](https://www.checkov.io/), and [pip-audit](https://github.com/pypa/pip-audit) run automatically as background checks on every pull request to guarantee compliance with our security baseline.

______________________________________________________________________

For additional assistance, please refer to our [SUPPORT.md](SUPPORT.md).

## File: CONTRIBUTING.md

# Contributing to GitVersioned

First off, thank you for considering contributing to GitVersioned! It's people like you that make this project great.

## Code of Conduct

By participating in this project, you are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). Please report any unacceptable behavior to the project team.

## Security Vulnerabilities

> [!IMPORTANT]
> **Please do not report security vulnerabilities through public GitHub issues.**

If you discover a security issue, please refer to our [Security Policy](SECURITY.md) for instructions on how to safely report it.

## How Can I Contribute?

There are many ways to contribute to GitVersioned, and not all of them involve writing code:

- **Reporting Bugs:** Help us improve by submitting detailed bug reports via our issue tracker.
- **Suggesting Features:** Propose new features or enhancements that could benefit the project.
- **Improving Documentation:** Fix typos, add examples, or write new guides.
- **Writing Code:** Fix bugs, implement features, or improve performance.
- **Helping Others:** Answer questions in [Discussions](https://github.com/markurtz/git-versioned/discussions) or issue comments.

For general questions and help, please see [SUPPORT.md](SUPPORT.md).

## Contributing Code

If you are contributing code, please follow these structured steps:

### 1. Development Setup

Before you start coding, please refer to our [Development Guide](DEVELOPING.md) for detailed instructions on:

- Setting up your local environment
- Installing dependencies
- Running the test suite
- Code formatting and linting standards

### 2. Finding an Issue

- Look for issues tagged with `good first issue` or `help wanted` if you are a new contributor.
- If you plan to work on a major feature, please open an issue or discussion first to talk it over with the maintainers.

### 3. Making Changes

1. **Fork the Repository:** Fork the git-versioned repository to your GitHub account.
1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/support-submodules`).
1. **Write Code:** Implement your changes, adhering to the project's coding standards.
1. **Write Tests:** Add unit tests or integration tests for your changes to ensure stability.
1. **Run Tests:** Ensure all tests and linters pass locally before committing.

### 4. Committing Your Changes

- Write clear, concise commit messages.
- We recommend using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add submodule support`, `fix: correct PEP 440 pre-release sorting`).

### 5. Submitting a Pull Request

1. **Push your branch:** `git push origin feat/support-submodules`.
1. **Open a Pull Request:** Open a PR against the `main` branch of the upstream repository.
1. **Fill out the PR Template:** Provide a clear description of your changes, link to any relevant issues (e.g., `Closes #42`), and complete any required checklists.
1. **Pass CI:** Ensure all GitHub Actions CI checks pass.
1. **Review:** Address any feedback from the maintainers. Once approved and checks pass, a maintainer will merge your PR.

## Licensing

By contributing to GitVersioned, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE).

## File: SECURITY.md

# Security Policy for GitVersioned

We take the security of GitVersioned seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability.

## Supported Versions

Please check the table below for the versions of GitVersioned that are currently being supported with security updates.

| Version   | Supported          |
| :-------- | :----------------- |
| `0.1.x`   | :white_check_mark: |
| `< 0.1.0` | :x:                |

## Reporting a Vulnerability

> [!IMPORTANT]
> **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**

If you discover a security vulnerability, please bring it to our attention right away using one of the following methods:

1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/git-versioned/security/advisories)** of this repository.
1. **Direct Message:** Send a message directly to the maintainer's GitHub user account, **[markurtz](https://github.com/markurtz)**, if applicable or through other provided direct pathways for the user.

### What to Include in Your Report

To help us resolve the issue quickly, please include the following information:

- **Type of vulnerability** (e.g., arbitrary code execution, path traversal, command injection).
- **Detailed description** of the vulnerability and its potential impact.
- **Step-by-step instructions** to reproduce the issue.
- **Proof of Concept (PoC)** code or screenshots, if available.
- **Environment details** (e.g., version of GitVersioned, OS, Python version, relevant Git configurations).

## Triage and Resolution Process

We will handle your report with strict confidentiality. Our process is as follows:

1. **Acknowledgment:** We will respond to your report as soon as possible, usually within a few business days.
1. **Triage:** We will investigate the issue and determine its validity and severity. We may contact you for further clarification.
1. **Fix:** If the vulnerability is verified, we will develop and test a patch.
1. **Disclosure:** We will coordinate with you to publicly disclose the vulnerability once a fix is released. We will publicly acknowledge your responsible disclosure, if you wish.

## Scope

**In Scope:**

- Vulnerabilities within the core GitVersioned codebase.
- Security issues resulting from our default configurations or execution paths.

**Out of Scope:**

- Theoretical issues without a reproducible PoC.
- Vulnerabilities in third-party dependencies that are not exploitable through GitVersioned.
- Issues requiring the victim to intentionally clone and run GitVersioned against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions.

*(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)*

## File: SUPPORT.md

# Support for GitVersioned

We are excited to have you use GitVersioned! If you need help, please follow these guidelines to ensure you get support quickly and efficiently.

## Security Vulnerabilities

> [!IMPORTANT]
> **Please do not report security vulnerabilities through public GitHub issues.**

If you have found a security vulnerability, please refer to our [Security Policy](SECURITY.md) for instructions on how to securely report it.

## Where to Find Help

Before reaching out, we recommend checking the following resources. Many common questions and issues are already covered there.

- **[Official Documentation](https://markurtz.github.io/git-versioned):** Installation instructions, onboarding guides, and tool references.
- **[GitHub Issues](https://github.com/markurtz/git-versioned/issues):** Search existing issues to see if someone else has already reported your problem or requested your feature. Feel free to add a "+1" reaction to existing issues to show your interest.
- **[GitHub Discussions](https://github.com/markurtz/git-versioned/discussions):** Search our discussions for Q&A, general advice, and community knowledge.

## Opening a New Issue

If you cannot find an answer in the documentation or existing issues, please open a new issue. To help us resolve your issue faster, please choose the correct venue:

| Issue Type             | Venue                                                                           | Description                                                                                                 |
| :--------------------- | :------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- |
| **Bug Report**         | [GitHub Issues](https://github.com/markurtz/git-versioned/issues/new)           | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs.          |
| **Feature Request**    | [GitHub Issues](https://github.com/markurtz/git-versioned/issues/new)           | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. |
| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/git-versioned/discussions/new) | Start a discussion for questions about how to use GitVersioned or for general advice.                       |

Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers.

## Commercial Support

At this time, there is no official commercial support available for GitVersioned. Support is provided on a best-effort basis by the open-source community and maintainers.
