Skip to content

Repository files navigation

se-codeowners

PyPIDocs SiteRepoPython 3.14License

CI-LeanCIDocs-DeployPre-ReleaseReleaseLinksDependabot

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 entryrequires_code_owner_review = false# opt in; generation refuses without an entry
[codeowners.role_handles]
owner = "@octocat"# a single handlemaintainer = ["@octocat", "@org/team"] # or several ownersdata-steward = "@octocat"
[[surface]]
id = "data-contract"name = "Data contract"paths = ["data/raw/**"]
oversight_role = "data-steward"# must have an entry in role_handlesoversight_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.tomlCODEOWNERSmeaning
README.md/README.mdone 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.pyone 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: localhooks:
- id: codeowners-up-to-datename: CODEOWNERS matches surfaces.tomlentry: uv run se-codeowners check --strictlanguage: systemfiles: ^(\.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