Metadata-Version: 2.4
Name: codeowners-drift
Version: 0.1.0
Summary: Which paths have no code owner, which owners have gone quiet, and which rules match nothing.
Author: mgkvibez
License: MIT
Keywords: codeowners,code-review,governance,git,audit,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# codrift

Audit a `CODEOWNERS` file against the repository it claims to govern: which paths have no
owner, which owners have gone quiet, which rules match nothing, and whether every new path
will start unowned.

Validators check the syntax of a CODEOWNERS file, or that an owner exists on GitHub. None of
them answer the question a review actually depends on: **will this path get a reviewer?**
A pattern matching no tracked file protects nothing. A rule pointing at a team that has not
committed in a year guarantees a stalled review. Neither is a syntax error, so nothing
catches either.

```
pipx install codeowners-drift
```

## What it reports

A repository whose CODEOWNERS drifted the way real ones do: no catch-all, a dead team, and a
rule for a directory that no longer exists.

```
$ codrift . --days 180
codrift: 5 finding(s) in ., worst high
3 rule(s) over 5 tracked file(s)

[HIGH] unowned-path .github/CODEOWNERS
  why:   no rule matches, so no reviewer is guaranteed

[HIGH] unowned-path migrations.py
  why:   no rule matches, so no reviewer is guaranteed

[HIGH] unowned-path tools/release.sh
  why:   no rule matches, so no reviewer is guaranteed

[MEDIUM] missing-default
  why:   no '*' catch-all rule, so new paths start unowned

[LOW] orphaned-pattern /legacy/
  rule:  line 4
  why:   matches no tracked file, so it protects nothing
```

The owner-side check, on a repository where everyone is covered but one owner has gone quiet:

```
$ codrift . --days 180
codrift: 2 finding(s) in ., worst medium
4 rule(s) over 5 tracked file(s)

[MEDIUM] quiet-owner
  why:   @adaenonche92 last committed 304 days ago, past the 180 day threshold

[LOW] orphaned-pattern /api/v2/*.py
  rule:  line 5
  why:   matches no tracked file, so it protects nothing
```

## Rules

| Severity | Rule | Why it matters |
|---|---|---|
| high | `unowned-path` | no rule matches this tracked file, so review is not guaranteed |
| medium | `missing-default` | no `*` catch-all, so every new path starts unowned |
| medium | `quiet-owner` | this owner has not committed inside the threshold |
| low | `orphaned-pattern` | this rule matches no tracked file, so it protects nothing |

## How it works

It reads the CODEOWNERS file (`CODEOWNERS`, `.github/CODEOWNERS` or `docs/CODEOWNERS`), takes
the tracked files from `git ls-files`, and applies the documented CODEOWNERS semantics: the
**last** matching rule wins, a leading slash anchors to the root, a trailing slash means the
directory and everything under it, `**` crosses directories while `*` does not, and a pattern
with no slash matches that name at any depth including the root.

Nothing is guessed about an owner who never committed. If git cannot find a commit by that
owner, the owner is reported as *unknown*, not inactive, because absent evidence is not the
same as absence of work. Team handles (`@org/team`) are skipped for the same reason: git has
no way to resolve a team to people.

## Options

| Flag | Meaning |
|---|---|
| `--days N` | how quiet an owner must be before it counts (default 180) |
| `--fail-on high\|medium\|low\|none` | lowest severity that exits 1 (default `high`) |
| `--max-unowned N` | cap the unowned paths listed, with a count of the rest (default 200) |
| `--json` | machine-readable report for a bot or an agent |

Exit codes: `0` clean, `1` findings at or above `--fail-on`, `2` usage error, no CODEOWNERS,
or not a git repository. The CODEOWNERS file itself is checked like any other path, since
protecting the file that decides who reviews is worth a rule.

## Limits

This is a static audit of a text file and a git history, so it cannot know what a team is
actually doing: an owner active in other repositories reads as quiet here. It does not verify
that an owner exists on GitHub, which needs the API; that is the one thing the existing
validators do well. It reads the first CODEOWNERS file it finds, not several.

The report says what it found, not that review is safe. `0` means no drift this check
recognises.

## Development

```bash
git clone https://github.com/mgkvibez/codeowners-drift
cd codeowners-drift
python3 -m pip install -e ".[dev]"
bash scripts/verify.sh     # the same six checks CI runs
python3 -m pytest -q       # 17 tests
```

Zero runtime dependencies, Python 3.9+.

## License

MIT
