Skip to content

chore: release 0.5.0 — real-world hardening - #7

Merged
smusali merged 9 commits into
mainfrom
release/0.5.0
Jul 4, 2026
Merged

chore: release 0.5.0 — real-world hardening#7
smusali merged 9 commits into
mainfrom
release/0.5.0

Conversation

@smusali

@smusali smusali commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

v0.5.0 is a hardening release driven by running every command against a large private production monorepo (24k commits, 12k active files) in both source and pip-installed modes, plus a full adversarial review of every module.

Correctness

  • Pattern-aware drift (checkowners/patterns.py): CODEOWNERS rules are matched with GitHub's real gitignore-style semantics (dir rules, globs, **, last-match-wins) instead of literal string comparison, which produced near-100% false drift on real files. Stale = rule matching no tracked file; changed = per-rule owner divergence; notes explain skipped comparisons (emails vs @Handles, team rules) instead of emitting noise.
  • validate now accepts everything GitHub accepts (relative patterns, owner-less exemption rules, escaped spaces) and rejects what GitHub rejects (!, [...]). It previously failed that repo's perfectly valid hand-written CODEOWNERS with 60+ errors; it now passes it.
  • Per-repo state, schema v3: ~/.checkowners/state/<repo-hash>.json with repo path verified on load. The old global state.json silently reused repo A's analysis in repo B.
  • Identity resolution: noreply emails map to @handles locally (no token, zero API calls — resolved 10 of 32 contributor identities for free), the search API result is cached with remembered misses, same-person emails merge, and bus factor counts people rather than email addresses. Bot authors (dependabot[bot]…) are excluded by default.

Performance

  • Parallel git blame + a prefilter that skips paths that can never produce owners: full 365-day analyze on the test monorepo went from a projected 80+ minutes to 1m24s (measured).
  • GitHub review scans bounded to the 200 most recently updated closed PRs and scoped to GITHUB_REPOSITORY (previously unbounded org-wide scans blew the rate limit).

UX and safety

  • generate consolidates uniform directories into dir/ rules (2,152 file entries → 1,563 lines incl. 156 dir rules on the test monorepo; this repo's own CODEOWNERS → * @smusali).
  • generate/sync refuse to overwrite a hand-written CODEOWNERS without --force — verified it no longer clobbers a hand-curated file.
  • --version, progress bar during analyze, cached-state hints, sync no-ops cleanly when already in sync, validate --json exits non-zero on invalid files, webhook failures can't crash the CLI.
  • Action: fails fast on shallow clones with fetch-depth: 0 guidance, installs the github extra, and maintains one PR comment updated in place (marked resolved when drift clears).

Packaging

  • PyGithub → optional [github] extra (plus [all]); unused GitPython dropped; core is pure git with three deps. Classifier bumped to Beta.

Test plan

  • 319 tests passing, coverage 88% (target 85%), ruff + mypy --strict + actionlint clean
  • Fresh-venv install matrix: core-only (no PyGithub/networkx), [graph], [graph,github]
  • Full-command sweep against the private test monorepo in both modes; --json outputs validated as JSON

After merging

  1. Create the GitHub Release v0.5.0publish.yml builds and publishes to PyPI via OIDC.
  2. The Marketplace listing picks up the new tag; docs already reference fortyOneTech/checkOwners@v0.5.0.

🤖 Generated with Claude Code

…testing

Correctness:
- Pattern-aware drift (patterns.py): gitignore-style CODEOWNERS matching
  replaces literal string comparison; missing/stale/changed recomputed
  truthfully, with notes for incomparable identities and team rules
- validate follows GitHub's real rules: relative patterns and owner-less
  rules are valid; ! and [...] rejected; escaped spaces parsed
- Per-repo state (schema v3) ends cross-repo contamination
- Identity: noreply emails resolve locally with no token, handle cache
  with remembered misses, same-person entries merge and bus factor is
  recomputed over distinct people; bot authors excluded by default

Performance:
- Parallel blame + qualified-path prefilter: full 365-day analyze on a
  24k-commit monorepo drops from 80+ min to under 2 min
- GitHub scans bounded (200 most recent closed PRs, repo-scoped)

UX & safety:
- generate consolidates uniform directories into dir/ rules
- generate/sync refuse to overwrite a hand-written CODEOWNERS sans --force
- --version flag, progress bar, cached-state hints, sync no-op success,
  validate --json exits non-zero on invalid files
- Action: shallow-clone guard, update-in-place PR comment, github extra

Packaging:
- PyGithub moved to [github] extra ([all] convenience); GitPython dropped
- Development Status :: 4 - Beta

Full changelog: docs/CHANGELOG.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

checkOwners: no drift detected

Previously reported drift has been resolved.

smusali and others added 8 commits July 3, 2026 19:28
…ckout, not PyPI

The in-repo example workflow ran the composite action against the latest
PyPI release, so a PR changing drift semantics was judged by the previous
version's code (0.4.0's literal matcher failing on the consolidated
CODEOWNERS this PR introduces). install_spec lets the dogfood pass
".[graph,github]" to test the code the PR actually contains; downstream
users omit it and keep the PyPI install.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d-space parsing

Iteration 1 of the whole-codebase cleanup pass:

- Drop dead OwnershipMap.handles_only() and github.map_owners(),
  both orphaned by the identity-merge rework
- Drop dead decay._path_is_adjacent() and the never-read
  drift.compare_to config option (docs and dogfood config updated;
  configs still carrying the key are ignored, not rejected)
- Single common_prefix_depth in expertise.py replaces identical
  copies in busfactor.py and decay.py
- patterns.py owns CODEOWNERS tokenization (strip_inline_comment,
  split_escaped) and parse_rules now honors backslash-escaped
  spaces, closing the gap where validate accepted
  'docs/getting\ started.md' but drift mis-parsed it
- Shared bounded iter_recent_closed_pulls() in github.py used by
  both PR-scan sites instead of two hand-rolled loops
- Trim triplicated bot-exclusion rationale to one docstring
- Type the test config helper properly instead of type: ignore

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'already in sync' branch and the generate/sync early refusal (which
must fire before the expensive analyze) only passed through mocks by
accident; both now have direct CLI tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… SECURITY.md and pre-commit

- checkowners/py.typed: the 'Typing :: Typed' classifier was claimed but
  the marker was never shipped, so downstream type checkers ignored the
  package's annotations
- .claude/ was leaking into the sdist (hatchling includes non-ignored
  files); now gitignored and excluded explicitly alongside .instructions
- SECURITY.md: private vulnerability reporting policy plus the token/
  webhook/state-dir security posture in one place
- .pre-commit-config.yaml (ruff check+format, yaml/whitespace hygiene,
  mypy --strict) with setup notes in CONTRIBUTING

Verified: rebuilt sdist+wheel are clean, py.typed present in both; full
a production monorepo command sweep re-run from the pip-installed 0.5.0 wheel
(all commands pass, overwrite guard refuses the hand-written CODEOWNERS,
365-day analyze+generate in 1m18s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es from prose

- config.py: _is_drift_mode/_is_severity TypeGuards derived from
  typing.get_args replace three cast() calls and the hand-maintained
  literal frozensets, so the Literal types are the single source of truth
- graph.py: typed assignment replaces cast(ModuleType, networkx)
- mypy per-module override for stub-less networkx replaces the two
  inline 'type: ignore[import-untyped]' suppressions
- tests/test_analyze.py: properly typed blame fake instead of an
  arg-type ignore
- Replace em dashes with commas/colons/parens in docstrings
  (generate, expertise), the onboarding markdown template, README,
  and the dogfood config, matching the repo's no-em-dash style; the
  Graphviz '--' edge operator and hatch's '--' argument separator are
  syntax, not prose, and stay

Verified no Api-cased identifiers and no single-file directories exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_recency_decay_constant_matches_expected compared two identical
math.pow expressions; an AST scan confirms it was the only test in the
suite that touches no checkowners code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ich markup in output

Running the full sweep live against a large production monorepo exposed two bugs:

- generate wrote Next.js dynamic-route paths ([companyId]) verbatim;
  GitHub treats [...] as an unsupported character range and ignores the
  line, silently un-owning the path. Bracket segments now become the
  valid * wildcard, colliding sanitized patterns merge their owners,
  and literal spaces are escaped on write.
- Rich parsed [companyId] in our own console output as markup and
  dropped it; every user-derived path/reason/handle rendered via Rich
  is now markup-escaped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sync commit rewrites CODEOWNERS, so git blame attributed the whole
file to whoever ran the tool, changing its inferred owner entry on
every subsequent run and preventing sync from ever reaching a stable
no-op. The three GitHub-recognized CODEOWNERS locations are now in the
default exclusions; inferring ownership of the generated file is noise
by definition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@smusali
smusali merged commit 4d50c17 into main Jul 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant