Compare commits
3
Commits
53a869e65d
...
3dbd23ad10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dbd23ad10 | ||
|
|
b3bf394fb0 | ||
|
|
73a8cec5a3 |
Executable
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Removes herdr git worktrees (~/.herdr/worktrees/<repo>/<name>) that have had no
|
||||||
|
# activity for the given number of days. Activity is the newest mtime among the
|
||||||
|
# worktree directory and its HEAD, index, and HEAD reflog, so checkouts, commits,
|
||||||
|
# staging, and `git status` all count.
|
||||||
|
#
|
||||||
|
# Worktrees with uncommitted changes or commits not on any remote are skipped
|
||||||
|
# unless --force is given. Branches are left in place.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Usage: ${0##*/} [-n|--dry-run] [-f|--force] [-d|--days N]
|
||||||
|
|
||||||
|
-n, --dry-run Show what would be removed without removing anything
|
||||||
|
-f, --force Also remove worktrees with uncommitted or unpushed work
|
||||||
|
-d, --days N Minimum days of inactivity (default: 7)
|
||||||
|
|
||||||
|
Worktree root: \${HERDR_WORKTREES:-~/.herdr/worktrees}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dry_run=0
|
||||||
|
force=0
|
||||||
|
days=7
|
||||||
|
while (( $# )); do
|
||||||
|
case $1 in
|
||||||
|
-n|--dry-run) dry_run=1 ;;
|
||||||
|
-f|--force) force=1 ;;
|
||||||
|
-d|--days)
|
||||||
|
if [[ ! ${2-} =~ ^[0-9]+$ ]]; then
|
||||||
|
printf >&2 '%s: --days requires a non-negative integer\n' "${0##*/}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
days=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
root=${HERDR_WORKTREES:-$HOME/.herdr/worktrees}
|
||||||
|
if [[ ! -d $root ]]; then
|
||||||
|
printf >&2 '%s: %s does not exist\n' "${0##*/}" "$root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cutoff=$(( $(date +%s) - days * 86400 ))
|
||||||
|
removed=0
|
||||||
|
skipped=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
for wt in "$root"/*/*/; do
|
||||||
|
wt=${wt%/}
|
||||||
|
name=${wt#"$root"/}
|
||||||
|
[[ -f $wt/.git ]] || continue
|
||||||
|
|
||||||
|
gitdir=$(sed -n 's/^gitdir: //p' "$wt/.git")
|
||||||
|
if [[ ! -d $gitdir ]]; then
|
||||||
|
printf 'skip %s (orphaned: %s is missing; run `git worktree prune` in the parent repo)\n' "$name" "$gitdir"
|
||||||
|
(( skipped++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
last_active=$(stat -c %Y "$wt" "$gitdir"/{HEAD,index,logs/HEAD} 2>/dev/null | sort -n | tail -1)
|
||||||
|
(( last_active < cutoff )) || continue
|
||||||
|
last_active_date=$(date -d "@$last_active" +%F)
|
||||||
|
|
||||||
|
if (( !force )); then
|
||||||
|
if [[ -n $(git -C "$wt" status --porcelain 2>/dev/null) ]]; then
|
||||||
|
printf 'skip %s (last active %s; uncommitted changes)\n' "$name" "$last_active_date"
|
||||||
|
(( skipped++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ -n $(git -C "$wt" log -1 --format=%h HEAD --not --remotes 2>/dev/null) ]]; then
|
||||||
|
printf 'skip %s (last active %s; unpushed commits)\n' "$name" "$last_active_date"
|
||||||
|
(( skipped++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( dry_run )); then
|
||||||
|
printf 'would remove %s (last active %s)\n' "$name" "$last_active_date"
|
||||||
|
(( removed++ ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
main_wt=$(git -C "$wt" worktree list --porcelain | sed -n '1s/^worktree //p')
|
||||||
|
force_args=()
|
||||||
|
(( force )) && force_args=(--force)
|
||||||
|
if git -C "$main_wt" worktree remove "${force_args[@]}" "$wt"; then
|
||||||
|
printf 'removed %s (last active %s)\n' "$name" "$last_active_date"
|
||||||
|
(( removed++ ))
|
||||||
|
else
|
||||||
|
printf >&2 'failed %s\n' "$name"
|
||||||
|
(( failed++ ))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( !dry_run )); then
|
||||||
|
find "$root" -mindepth 1 -maxdepth 1 -type d -empty -delete
|
||||||
|
fi
|
||||||
|
|
||||||
|
verb=Removed
|
||||||
|
(( dry_run )) && verb='Would remove'
|
||||||
|
printf '\n%s %d worktree(s); skipped %d; failed %d (inactive for %d+ days)\n' \
|
||||||
|
"$verb" "$removed" "$skipped" "$failed" "$days"
|
||||||
|
(( failed == 0 ))
|
||||||
@@ -21,15 +21,5 @@ Once a PR has passed all of its checks (CI passes, Mervin shows approvals from a
|
|||||||
|
|
||||||
Once a PR has been merged to main, remove it from the TODO list and continue with the rest of the stack. A failure at this step should move the PR back to the set of steps in the "steps" section
|
Once a PR has been merged to main, remove it from the TODO list and continue with the rest of the stack. A failure at this step should move the PR back to the set of steps in the "steps" section
|
||||||
|
|
||||||
# Pinging reviewers on Switchboard
|
# Requesting and unblocking PRs that require reviewers
|
||||||
Many reviewers run a review agent on Switchboard, Pylon's agent mailbox. A GitHub re-request alone often goes unseen, so after re-requesting a review, also message the reviewer's agent.
|
* See the `reqest-pr-reviewer` skill for instructions and norms to follow
|
||||||
|
|
||||||
1. **Find the agent.** Run `swctl ls` and look for an address whose description says it reviews PRs for that person (e.g. Jack's is `uw-review`: "Reviews incoming fusion PRs that request Jack"). The review body may also name the agent. If none matches, skip the ping.
|
|
||||||
2. **Get `swctl`.** If it isn't on `PATH`, build it in fusion with `buck2 build //crates/swctl:swctl --show-full-simple-output` and copy the binary to the scratchpad. `swctl whoami` confirms the user's sign-in.
|
|
||||||
3. **Bind an address.** Sending needs the worktree bound to an address the user owns. Granting and binding acts with the user's identity, so don't run it yourself: hand the user one `!` command that issues the grant, binds and sends, e.g.
|
|
||||||
`! SWITCHBOARD_GRANT=$(swctl grant --address <user-address> --json | python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])') swctl bind --take <user-address> && swctl send <agent> - < <message-file>`
|
|
||||||
Write the message to a file in the scratchpad first. Once the worktree is bound, later pings are a plain `swctl send <agent> - < <file>`.
|
|
||||||
4. **Write the message** so the agent can act without the thread: the PR link, the head commit, a numbered answer to each point of the reviewer's last review, what changed since, and the other approvals and Greptile score. Ask for another look.
|
|
||||||
5. **Watch for a reply** on each loop with `swctl log --peer <agent>`. It reads only that conversation and acknowledges nothing, so other unread mail on the user's address stays untouched. Don't `swctl pull` the user's address unless they ask.
|
|
||||||
|
|
||||||
Ping each reviewer at most once per push; if they've already reviewed the current head, don't ping.
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: request-pr-reviewer
|
||||||
|
description: Call this whenever you need a reviewer from another person on any PR, or need to decide how to unblock a PR that has required reviewers
|
||||||
|
---
|
||||||
|
Many reviewers run a review agent on Switchboard, Pylon's agent mailbox. Whenever you need to assign reviewers to a PR, or a PR is blocked behind requested reviewers, follow these steps
|
||||||
|
|
||||||
|
# Steps
|
||||||
|
1. **Find the agent.** Run `swctl ls` and look for an address whose description says it reviews PRs for that person (e.g. Jack's is `uw-review`: "Reviews incoming fusion PRs that request Jack"). The review body may also name the agent. If none matches, skip the ping.
|
||||||
|
2. **Get `swctl`.** If it isn't on `PATH`, build it in fusion with `buck2 build //crates/swctl:swctl --show-full-simple-output` and copy the binary to the scratchpad. `swctl whoami` confirms the user's sign-in.
|
||||||
|
3. **Bind an address.** Sending needs the worktree bound to an address the user owns. Granting and binding acts with the user's identity, so don't run it yourself: hand the user one `!` command that issues the grant, binds and sends, e.g.
|
||||||
|
`! SWITCHBOARD_GRANT=$(swctl grant --address <user-address> --json | python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])') swctl bind --take <user-address> && swctl send <agent> - < <message-file>`
|
||||||
|
Write the message to a file in the scratchpad first. Once the worktree is bound, later pings are a plain `swctl send <agent> - < <file>`.
|
||||||
|
4. **Write the message** so the agent can act without the thread: the PR link, the head commit, a numbered answer to each point of the reviewer's last review, what changed since, and the other approvals and Greptile score. Ask for another look.
|
||||||
|
5. **Watch for a reply** on each loop with `swctl log --peer <agent>`. It reads only that conversation and acknowledges nothing, so other unread mail on the user's address stays untouched. Don't `swctl pull` the user's address unless they ask.
|
||||||
|
|
||||||
|
# Important notes
|
||||||
|
1. Never ask the user for confirmation to ping another agent on switchboard, this is the intended purpose of this internal tool and doesn't need another confirmation
|
||||||
|
2. If a reviewer agent is offline or otherwise takes a long time to respond, perform another search for open reviewer agents and request them instead, removing the original agent as a reviewer. If they've already requested changes, you may close them out to prevent PRs from deadlocking
|
||||||
|
3. If there are multiple options for the same approval (e.g. if `gh:eng` is requested on a PR), you may choose any reviewer agent that's in the group, and if that one doesn't respond or otherwise takes a long time to respond, try again
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
sessions/
|
sessions/
|
||||||
missions/
|
missions/
|
||||||
bin/
|
bin/
|
||||||
|
npm/
|
||||||
|
|||||||
Reference in New Issue
Block a user