Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/update-codeowners.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Update CODEOWNERS

on:
schedule:
# Run weekly on Monday at 00:00 UTC
- cron: '0 0 * * 1'
workflow_dispatch: # Allow manual triggering

permissions:
contents: write
pull-requests: write

jobs:
update-codeowners:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/[email protected]
with:
fetch-depth: 0 # Fetch full history for accurate analysis
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Run CODEOWNERS analyzer
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python scripts/codeowner_analyzer.py \
--output .github/CODEOWNERS \
--depth 3 \
--min-commits 2 \
--days-back 365

- name: Check for changes
id: check_changes
run: |
if git diff --quiet .github/CODEOWNERS; then
echo "changed=false" >> $GITHUB_OUTPUT
echo "No changes detected in CODEOWNERS"
else
echo "changed=true" >> $GITHUB_OUTPUT
echo "Changes detected in CODEOWNERS"
fi

- name: Create Pull Request
if: steps.check_changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: |
chore: update CODEOWNERS based on git history

Auto-generated CODEOWNERS update based on commit activity over the last 365 days.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
branch: auto-update-codeowners
delete-branch: true
title: 'chore: Update CODEOWNERS'
body: |
## Summary

This PR updates the CODEOWNERS file based on git commit history analysis from the last 365 days.

## Changes

- Updated `.github/CODEOWNERS` with current code ownership based on:
- Commit frequency
- File coverage
- Commit recency

## How to Review

1. Review the changes to `.github/CODEOWNERS`
2. Verify that the assigned owners are appropriate for each module
3. Make manual adjustments if needed before merging

## Notes

- This is an automated PR generated weekly
- Minimum commits threshold: 2
- Analysis period: 365 days
- Directory depth: 3 levels

---

πŸ€– This PR was automatically generated by the [update-codeowners workflow](.github/workflows/update-codeowners.yml)
labels: |
automated
maintenance
assignees: |

reviewers: |
14 changes: 13 additions & 1 deletion scripts/codeowner_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(
github_token: Optional[str] = None,
use_api: bool = True,
allowed_users: Optional[List[str]] = None,
max_depth: int = 3,
):
"""
Initialize the code owners analyzer.
Expand All @@ -43,10 +44,12 @@ def __init__(
github_token: Optional GitHub API token for higher rate limits
use_api: Whether to use GitHub API for email lookups (default: True)
allowed_users: Optional list of GitHub usernames to include (filters out others)
max_depth: Maximum directory depth for module detection (default: 3)
"""
self.repo_path = Path(repo_path).resolve()
self.min_commits = min_commits
self.days_back = days_back
self.max_depth = max_depth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's good practice to validate input parameters. The max_depth should be a positive integer. A value of 0 or less would result in no modules being detected, which might be unexpected. Consider adding a check to ensure max_depth is positive.

        if max_depth <= 0:
            raise ValueError("max_depth must be a positive integer.")
        self.max_depth = max_depth

self.module_owners: DefaultDict[str, DefaultDict[str, int]] = defaultdict(
lambda: defaultdict(int)
)
Expand Down Expand Up @@ -439,8 +442,10 @@ def get_modules(self) -> List[str]:

if file_ext in relevant_extensions:
# Add the directory and all parent directories as modules
# Limited by max_depth
path_parts = Path(dir_path).parts
for i in range(1, len(path_parts) + 1):
max_parts = min(len(path_parts), self.max_depth)
for i in range(1, max_parts + 1):
module = "/".join(path_parts[:i])
if not self.should_exclude(module):
modules.add(module)
Expand Down Expand Up @@ -773,6 +778,12 @@ def main() -> int:
"--allowed-users-file",
help="File containing allowed GitHub usernames, one per line",
)
parser.add_argument(
"--depth",
type=int,
default=3,
help="Maximum directory depth for module detection (default: 3)",
)

args = parser.parse_args()

Expand Down Expand Up @@ -811,6 +822,7 @@ def main() -> int:
github_token=args.github_token,
use_api=not args.no_api,
allowed_users=allowed_users,
max_depth=args.depth,
)
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
Expand Down