Skip to content

Commit 78810a2

Browse files
authored
feat: branch-triage GH Action + weekly workflow (#209)
Adds .github/actions/branch-triage/ — a composite action that classifies, rebases, merges, and prunes branches: - triage.sh: classifies remote branches into OPEN / MERGED / SUPERSEDED / REVIEW, deletes MERGED+SUPERSEDED, rebases+merges OPEN PRs one at a time, skips REVIEW branches with a report - action.yml: composite action wrapping triage.sh with inputs for base-branch, merge-strategy, dry-run, stale-days, github-token - README.md: usage, inputs/outputs, conflict-resolution policy - .github/workflows/branch-triage.yml: example workflow — scheduled Monday 09:00 UTC + manual workflow_dispatch with dry-run toggle Conflict resolution: files not owned by the branch take base's version automatically; files owned by the branch abort the rebase with a warning so security/logic changes are never silently dropped. Closes #196
1 parent 8eb998e commit 78810a2

4 files changed

Lines changed: 448 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# branch-triage action
2+
3+
Composite GitHub Action that classifies, rebases, merges, and prunes
4+
branches in a repository. Designed for repos that accumulate many
5+
short-lived feature branches and need periodic cleanup.
6+
7+
## What it does
8+
9+
1. **Classifies** every remote branch into one of four categories:
10+
11+
| Category | Definition | Action |
12+
|---|---|---|
13+
| OPEN | Has an open PR | Rebase onto base + merge |
14+
| MERGED | Has a merged PR | Delete local + remote |
15+
| SUPERSEDED | No PR, 0 unique commits vs base | Delete |
16+
| REVIEW | No PR, >0 unique commits | Skip — report only |
17+
18+
2. **Deletes** MERGED and SUPERSEDED branches (remote + local).
19+
3. **Rebases** each OPEN-PR branch onto the base, then merges the PR.
20+
4. **Skips** REVIEW branches — reports them in the job summary so a human can decide.
21+
5. **Writes** a Markdown summary to the GitHub job summary.
22+
23+
## Inputs
24+
25+
| Input | Required | Default | Description |
26+
|---|---|---|---|
27+
| `base-branch` | no | `main` | Protected base branch |
28+
| `merge-strategy` | no | `squash` | `squash`, `merge`, or `rebase` |
29+
| `dry-run` | no | `false` | Classify only; no deletes or merges |
30+
| `stale-days` | no | `30` | Zero-commit branches older than this are SUPERSEDED (0 = off) |
31+
| `github-token` | no | `${{ github.token }}` | Token with repo write + PR write |
32+
33+
## Outputs
34+
35+
| Output | Description |
36+
|---|---|
37+
| `merged-count` | PRs merged |
38+
| `deleted-count` | Branches deleted |
39+
| `kept-count` | REVIEW branches left untouched |
40+
41+
## Usage
42+
43+
```yaml
44+
# Scheduled weekly + manual dispatch
45+
on:
46+
schedule:
47+
- cron: "0 9 * * 1" # every Monday 09:00 UTC
48+
workflow_dispatch:
49+
inputs:
50+
dry-run:
51+
default: "false"
52+
53+
permissions:
54+
contents: write
55+
pull-requests: write
56+
57+
jobs:
58+
triage:
59+
runs-on: ubuntu-latest
60+
steps:
61+
- uses: actions/checkout@v4
62+
with:
63+
fetch-depth: 0
64+
- uses: ./.github/actions/branch-triage
65+
with:
66+
dry-run: ${{ inputs.dry-run || 'false' }}
67+
github-token: ${{ secrets.GITHUB_TOKEN }}
68+
```
69+
70+
## Conflict resolution policy
71+
72+
When rebasing an OPEN-PR branch:
73+
- Files **not owned by the branch** (not in its diff vs base) → take base version automatically.
74+
- Files **owned by the branch** → rebase is aborted and the PR is skipped with a warning; resolve manually.
75+
76+
This avoids silently dropping logic or security changes the branch intentionally introduces.
77+
78+
## Relationship to Coven skill
79+
80+
The same workflow is available as an internal OpenCoven familiar skill at
81+
`~/.coven/skills/git-branch-triage/SKILL.md`. The skill version is
82+
interactive (asks before destructive actions); this Action version is
83+
automated and runs headless on a schedule.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Branch Triage
2+
description: >
3+
Classify, rebase, merge, and prune git branches.
4+
Merges open PRs, deletes merged/superseded branches, removes orphaned
5+
worktrees, and writes a Markdown summary to the job summary.
6+
7+
inputs:
8+
base-branch:
9+
description: The protected base branch to measure all others against.
10+
required: false
11+
default: main
12+
merge-strategy:
13+
description: >
14+
How to merge open PRs: "squash" (default), "merge", or "rebase".
15+
required: false
16+
default: squash
17+
dry-run:
18+
description: >
19+
If "true", classify and report but do not delete branches or merge PRs.
20+
required: false
21+
default: "false"
22+
stale-days:
23+
description: >
24+
Branches with no unique commits vs base AND last-commit older than this
25+
many days are classified as SUPERSEDED without asking.
26+
Set to 0 to disable age check (unique-commit check only).
27+
required: false
28+
default: "30"
29+
github-token:
30+
description: >
31+
GitHub token with repo write access (contents + pull-requests).
32+
Defaults to the standard GITHUB_TOKEN.
33+
required: false
34+
default: ${{ github.token }}
35+
36+
outputs:
37+
merged-count:
38+
description: Number of PRs merged.
39+
value: ${{ steps.triage.outputs.merged_count }}
40+
deleted-count:
41+
description: Number of branches deleted (merged + superseded).
42+
value: ${{ steps.triage.outputs.deleted_count }}
43+
kept-count:
44+
description: Number of REVIEW branches left untouched (need manual decision).
45+
value: ${{ steps.triage.outputs.kept_count }}
46+
47+
runs:
48+
using: composite
49+
steps:
50+
- name: Configure git identity
51+
shell: bash
52+
run: |
53+
git config --global user.email "actions@github.com"
54+
git config --global user.name "GitHub Actions"
55+
56+
- name: Run branch triage
57+
id: triage
58+
shell: bash
59+
env:
60+
GH_TOKEN: ${{ inputs.github-token }}
61+
BASE_BRANCH: ${{ inputs.base-branch }}
62+
MERGE_STRATEGY: ${{ inputs.merge-strategy }}
63+
DRY_RUN: ${{ inputs.dry-run }}
64+
STALE_DAYS: ${{ inputs.stale-days }}
65+
run: |
66+
"${{ github.action_path }}/triage.sh"

0 commit comments

Comments
 (0)