Skip to main content

Generate a GitHub CODEOWNERS file from role-based oversight metadata in .accountability/surfaces.toml.

Project description

se-codeowners

PyPI Docs Site Repo Python 3.14 License

CI-Lean CI Docs-Deploy Pre-Release Release Links Dependabot

Standalone CLI utility that implements GitHub CODEOWNERS projection

Overview

Generate a GitHub CODEOWNERS file from the role-based oversight metadata in .accountability/surfaces.toml.

The surfaces file is the advisory rationale: it names each accountability surface, the paths it covers, and the oversight_role responsible for it. se-codeowners resolves each role to a GitHub handle or team via [codeowners.role_handles] and emits the corresponding CODEOWNERS entries.

The generated CODEOWNERS file provides GitHub review routing. It becomes a merge gate only when repository branch protection or rulesets require code-owner review.

Install

uv tool install se-codeowners

Uses the standard-library tomllib. No runtime dependencies.

Run Without Installing

uvx se-codeowners --help

Usage

# Render to stdout
uv run se-codeowners generate

# Write the file
uv run se-codeowners generate --output .github/CODEOWNERS

# Fail if any role still maps to a placeholder handle (e.g. `@OWNER`):
uv run se-codeowners generate --strict --output .github/CODEOWNERS

# Verify
uv run se-codeowners check

Both subcommands accept --surfaces PATH; check also accepts --codeowners PATH. The defaults are .accountability/surfaces.toml and .github/CODEOWNERS.

What the surfaces file must contain

[codeowners]
informs = true                           # opt in; generation refuses without an entry
requires_code_owner_review = false       # opt in; generation refuses without an entry

[codeowners.role_handles]
owner = "@octocat"                       # a single handle
maintainer = ["@octocat", "@org/team"]   # or several owners
data-steward = "@octocat"

[[surface]]
id = "data-contract"
name = "Data contract"
paths = ["data/raw/**"]
oversight_role = "data-steward"          # must have an entry in role_handles
oversight_role = "data-steward"
codeowners_order = 100

Surfaces without an oversight_role are skipped. A role used by a surface but missing from role_handles is a hard error.

Projected surfaces must declare codeowners_order. Lower values emit earlier; later matching CODEOWNERS patterns take precedence.

Path translation

surfaces.toml uses repository-root-relative globs. CODEOWNERS uses gitignore-style patterns with a smaller feature set, so patterns are converted:

surfaces.toml CODEOWNERS meaning
README.md /README.md one file at the repo root
docs/** /docs/** a directory, recursively
sql/duckdb/** /sql/duckdb/** a nested directory, recursively
docs/* /docs/* direct children only
src/app/case.py /src/app/case.py one nested file

Patterns CODEOWNERS cannot express:

  • negation (!),
  • single-character (?),
  • character ranges ([...]), or a
  • ** segment anywhere but a trailing /**

These are rejected at generation time with a message naming the offending pattern, so the failure surfaces during generation.

GitHub applies the last matching pattern for a path. Entries are emitted by codeowners_order, then surface id.

Keeping it in sync (CI / pre-commit)

Add a hook to the consuming repository so a stale CODEOWNERS fails review:

# .pre-commit-config.yaml in the repo that owns surfaces.toml
- repo: local
  hooks:
    - id: codeowners-up-to-date
      name: CODEOWNERS matches surfaces.toml
      entry: uv run se-codeowners check --strict
      language: system
      files: ^(\.accountability/surfaces\.toml|\.github/CODEOWNERS)$
      pass_filenames: false

Layout

se-codeowners/
├── src/se_codeowners/
│   ├── __init__.py        public API: load_surfaces, render_codeowners, ...
│   ├── __main__.py        module entry: python -m se_codeowners (PATH-independent)
│   ├── _narrow.py         boundary narrowing: tomllib Any -> typed values
│   ├── model.py           frozen dataclasses (Surface, SurfacesDoc)
│   ├── load.py            parse + validate surfaces.toml into the model
│   ├── translate.py       surface glob -> CODEOWNERS pattern
│   ├── generate.py        SurfacesDoc -> CODEOWNERS text
│   └── cli.py             generate / check subcommands
└── tests/

Dependency direction is one-way:

  • _narrow and model depend on nothing in the package;
  • load depends on both;
  • translate is standalone;
  • generate depends on model and translate;
  • cli depends on load and generate.

No raw Any from tomllib escapes _narrow/load.

Developer Command Reference

Show command reference

In a machine terminal

Open a machine terminal where you want the project:

git clone https://github.com/structural-explainability/se-codeowners

cd se-codeowners
code .

In a VS Code terminal

Use VS Code Menu: View / Command Palette / Developer: Reload Window to refresh.

uv self update
uv python pin 3.14
uv lock --upgrade
uv sync --extra dev --extra docs --upgrade

uvx pre-commit install

uv run se-codeowners --help
uv run se-codeowners generate --help
uv run se-codeowners check --help

# validate manifest file
uvx se-manifest-schema validate-manifest --path SE_MANIFEST.toml --strict

git add -A
uvx pre-commit run --all-files
# repeat if changes were made
uvx pre-commit run --all-files

uv run python -m pyright
uv run python -m pytest
uv run python -m zensical build

# check import layers
uvx --with-editable . --from import-linter lint-imports --config .github/.importlinter

# check complexity; no output is good (all A or B)
uvx radon cc src/se_codeowners -s -a -n C

uv build
uvx twine check dist/*

# save progress
git add -A
git commit -m "update"
git push -u origin main

Authority Manifest

.accountability/surfaces.toml

Citation

CITATION.cff

License

MIT

Repository Manifest

SE_MANIFEST.toml

Project details


Download files

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

Source Distribution

se_codeowners-0.2.0.tar.gz (69.4 kB view details)

Uploaded Source

Built Distribution

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

se_codeowners-0.2.0-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file se_codeowners-0.2.0.tar.gz.

File metadata

  • Download URL: se_codeowners-0.2.0.tar.gz
  • Upload date:
  • Size: 69.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for se_codeowners-0.2.0.tar.gz
Algorithm Hash digest
SHA256 00dc8fb4cc15e2e681ad0b885d54d3ea5153496b702503c76b45b5e211a5d8d2
MD5 26e197dc2e6bce07c3c5d0726b744b8d
BLAKE2b-256 ee0beee8ab237635fc8cdf13e7a12b580950dcae9f1e0c6395a9a9dfda9ddd00

See more details on using hashes here.

Provenance

The following attestation bundles were made for se_codeowners-0.2.0.tar.gz:

Publisher: release-pypi.yml on structural-explainability/se-codeowners

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

File details

Details for the file se_codeowners-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: se_codeowners-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 14.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for se_codeowners-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 143ecc1908863b4de793c3efac3188120e8b3a14ec87a6c07027e621bfcde905
MD5 89b3f52ae108b6b7c17030f19622c713
BLAKE2b-256 a9fe7c1fb1a0dfeff751e3a285bf0f13d16d105a964bb7e1ccd1145085d3e792

See more details on using hashes here.

Provenance

The following attestation bundles were made for se_codeowners-0.2.0-py3-none-any.whl:

Publisher: release-pypi.yml on structural-explainability/se-codeowners

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page