chore: release 0.5.0 — real-world hardening - #7
Merged
Merged
Conversation
…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>
checkOwners: no drift detectedPreviously reported drift has been resolved. |
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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.!,[...]). It previously failed that repo's perfectly valid hand-written CODEOWNERS with 60+ errors; it now passes it.~/.checkowners/state/<repo-hash>.jsonwith repo path verified on load. The old globalstate.jsonsilently reused repo A's analysis in repo B.@handleslocally (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
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_REPOSITORY(previously unbounded org-wide scans blew the rate limit).UX and safety
generateconsolidates uniform directories intodir/rules (2,152 file entries → 1,563 lines incl. 156 dir rules on the test monorepo; this repo's own CODEOWNERS →* @smusali).generate/syncrefuse 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,syncno-ops cleanly when already in sync,validate --jsonexits non-zero on invalid files, webhook failures can't crash the CLI.fetch-depth: 0guidance, installs thegithubextra, and maintains one PR comment updated in place (marked resolved when drift clears).Packaging
[github]extra (plus[all]); unused GitPython dropped; core is pure git with three deps. Classifier bumped to Beta.Test plan
ruff+mypy --strict+actionlintclean[graph],[graph,github]--jsonoutputs validated as JSONAfter merging
v0.5.0—publish.ymlbuilds and publishes to PyPI via OIDC.fortyOneTech/checkOwners@v0.5.0.🤖 Generated with Claude Code