From 6a118aa4fb7425d8f86eb57249d4c806c990163f Mon Sep 17 00:00:00 2001 From: Itay Donanhirsh Date: Mon, 8 Dec 2025 19:53:58 -0800 Subject: [PATCH 1/2] work --- github_wait/Makefile | 20 ++++ github_wait/README.md | 188 ++++++++++++++++++++++++++++++++++++ github_wait/autokitteh.yaml | 44 +++++++++ github_wait/handlers.py | 140 +++++++++++++++++++++++++++ 4 files changed, 392 insertions(+) create mode 100644 github_wait/Makefile create mode 100644 github_wait/README.md create mode 100644 github_wait/autokitteh.yaml create mode 100644 github_wait/handlers.py diff --git a/github_wait/Makefile b/github_wait/Makefile new file mode 100644 index 00000000..b990884a --- /dev/null +++ b/github_wait/Makefile @@ -0,0 +1,20 @@ +UVX=uvx --with autokitteh --with PyGithub + +.PHONY: all +all: lint typecheck format + +.PHONY: lint +lint: + $(UVX) ruff check --config ../pyproject.toml --ignore I001 + +.PHONY: format +format: + $(UVX) ruff format --check --config ../pyproject.toml + +.PHONY: typecheck +typecheck: + $(UVX) mypy --follow-untyped-imports . + +.PHONY: deploy +deploy: + akl deploy --manifest autokitteh.yaml diff --git a/github_wait/README.md b/github_wait/README.md new file mode 100644 index 00000000..95576c10 --- /dev/null +++ b/github_wait/README.md @@ -0,0 +1,188 @@ +--- +title: GitHub Wait - PR/Issue State Management Bot +description: Slash command bot for managing waiting states on PRs and issues with automatic label updates +integrations: ["github"] +categories: ["Productivity", "DevOps"] +tags: ["github", "pull_requests", "issue_management", "slash_commands", "labels"] +--- + +# GitHub Wait - PR/Issue State Management Bot + +[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?name=github_wait) + +A GitHub bot that helps reviewers track PRs that are waiting for author updates. Reviewers can mark PRs with `/wait` after completing their review, and the bot automatically removes the label when the author pushes new changes - making it easy to see at a glance which PRs are ready for re-review. + +## Features + +- **Quick PR Triage**: Reviewers can instantly see which PRs have been updated and need attention +- **Automatic Label Removal**: Labels disappear when authors push new code - no manual cleanup needed +- **Slash Command Interface**: Simple `/wait` or `/wait-any` commands in review comments +- **Two Wait Modes**: + - `/wait` - removed only when author pushes code (ideal for "changes requested") + - `/wait-any` - removed when author comments or pushes code (for discussions) +- **Visual Feedback**: Emoji reactions confirm command execution +- **Works Everywhere**: Use in PR reviews, review comments, or regular comments + +## Why? + +Active reviewers often manage dozens of PRs simultaneously. The problem: **when looking at a PR list, it's hard to tell which PRs have been updated since your last review.** + +GitHub's native notifications don't give you an at-a-glance view of what needs attention. This bot solves that by: + +- **After reviewing**: Mark the PR with `/wait` to indicate you're waiting for the author +- **Author updates**: When they push new code, the label automatically disappears +- **Quick scanning**: Filter your PR list by the waiting label to see what needs re-review +- **No manual cleanup**: Labels remove themselves - no stale markers cluttering your views + +For reviewers handling many PRs, this creates a self-maintaining "reviewed, waiting for author" queue that updates automatically as authors respond. + +### What [AutoKitteh](https://autokitteh.com) Provides + +- Event-driven GitHub integration +- No server configuration needed +- Built-in GitHub API client +- Automatic event filtering +- Production ready deployment + +## How It Works + +### Typical Reviewer Workflow + +1. **Review a PR**: Complete your code review and request changes +2. **Mark as waiting**: Add `/wait` in your review comment +3. **Bot adds label**: The `waiting` label appears on the PR +4. **Author pushes code**: When they address your feedback and push commits +5. **Label auto-removes**: The bot detects the push and removes the label +6. **You see it's ready**: The PR disappears from your "waiting" filter - time to review again! + +### Wait Modes + +**`/wait` (waiting for code changes)** - Best for most reviews +- Adds `waiting` label +- Label removed only when author pushes new code +- Ideal after requesting changes or when specifically waiting for code updates + +**`/wait-any` (waiting for any response)** - For discussions +- Adds `waiting:any` label +- Label removed when author comments OR pushes code +- Use when you need clarification or are waiting for discussion to continue + +## Usage + +### After Completing a Review + +When you finish reviewing a PR and are waiting for the author to make changes: + +``` +/wait +``` + +This adds the `waiting` label to the PR. When the author pushes new code, the label automatically disappears. + +**Pro tip**: Filter your PR list by `label:waiting` to see all PRs you've reviewed that are still waiting for author updates. When the label disappears, you know it's time to re-review! + +### For Discussion-Based Reviews + +When you're waiting for the author to respond to questions or clarify something: + +``` +/wait-any +``` + +This adds the `waiting:any` label. It will be removed when the author either comments or pushes code. + +### Switching Wait Modes + +You can change your mind by using the other command - the bot automatically switches labels: + +``` +/wait # Adds "waiting" label +/wait-any # Switches to "waiting:any" label +``` + +## Deployment + +Deploy using the AutoKitteh CLI or web interface: + +[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?name=github_wait) + +### Command Line Deployment + +1. **Install the CLI**: https://docs.autokitteh.com/get_started/install +2. **Authenticate**: `ak auth login` +3. **Deploy**: `make deploy` +4. **Initialize connection**: Log in to https://autokitteh.cloud and initialize the GitHub connection +5. **Configure repository**: Set up the connection to watch your GitHub repository + +## Connections + +- **github** (required): GitHub integration for receiving events and managing labels + +## Implementation Details + +### Core Files + +- **`handlers.py`** (`handlers.py:23`): Main event handlers + - `on_command()`: Processes `/wait` and `/wait-any` commands + - `on_pull_request_sync()`: Removes waiting labels when new code is pushed + - `on_issue_comment()`: Removes `waiting:any` label on new issue comments + - `on_pull_request_review_comment()`: Removes `waiting:any` label on new PR review comments + - `on_pull_request_review()`: Removes `waiting:any` label on new PR reviews + - `_label()`: Helper function for adding and removing labels + +### How It Uses AutoKitteh + +The system uses AutoKitteh's event-driven architecture: + +```python +from autokitteh import Event +from autokitteh.github import github_client +``` + +1. **Event Triggers** (`autokitteh.yaml:10`): Multiple GitHub event types + + ```yaml + triggers: + - name: command + connection: github + event_type: slash_command + call: handlers.py:on_command + filter: "data.command.name in ['wait', 'wait-any'] && (data.actual_event_type in ['issue_comment', 'pull_request_review_comment', 'pull_request_review'])" + ``` + +2. **Event Filtering**: Smart filtering based on event types and slash commands + +3. **GitHub API Integration** (`handlers.py:10`): Built-in GitHub client + ```python + github = github_client("github") + repo = github.get_repo(data.repository.full_name) + issue = repo.get_issue(number=data.issue.number) + issue.add_to_labels("waiting") + ``` + +### Labels Used + +The bot uses two labels: + +- **`waiting`**: Indicates waiting for code changes (push events only) +- **`waiting:any`**: Indicates waiting for any activity (comments or code changes) + +These labels should be created in your repository. The bot will create them automatically on first use if they don't exist. + +### Smart Activity Detection + +The bot intelligently handles different scenarios to prevent false positives: + +- **Reviewer Comments**: When reviewers add more comments (including wait commands), labels stay in place +- **Author Comments**: Comments from the author remove the `waiting:any` label (but not `waiting`) +- **Code Pushes**: New commits from the author remove both `waiting` and `waiting:any` labels +- **Reviews**: New review submissions remove the `waiting:any` label + +This means reviewers can continue discussing without accidentally removing the waiting state - only author activity triggers label removal. + +### Bot Responses + +The bot provides feedback through: + +- **Emoji reactions**: `+1` emoji on command comments to confirm execution +- **Automatic label management**: Labels appear and disappear based on activity diff --git a/github_wait/autokitteh.yaml b/github_wait/autokitteh.yaml new file mode 100644 index 00000000..88cd2beb --- /dev/null +++ b/github_wait/autokitteh.yaml @@ -0,0 +1,44 @@ +version: v2 + +project: + name: github_wait + + connections: + - name: github + integration: github + + triggers: + - # On slash command /wait or /wait-any, add appropriate labels. + name: command + connection: github + event_type: slash_command + call: handlers.py:on_command + filter: "data.command.name in ['wait', 'wait-any'] && (data.actual_event_type in ['issue_comment', 'pull_request_review_comment', 'pull_request_review'])" + + - # On a new issue comment, remove wait-any labels unless the comment is a command. + name: issue_comment + connection: github + event_type: issue_comment + call: handlers.py:on_issue_comment + filter: "data.action == 'created'" + + - # On a new review comment, remove wait-any labels unless the comment is a command. + name: review_comment + connection: github + event_type: pull_request_review_comment + call: handlers.py:on_pull_request_review_comment + filter: "data.action == 'created'" + + - # On a new review , remove wait-any labels unless the comment is a command. + name: review_submitted + connection: github + event_type: pull_request_review + call: handlers.py:on_pull_request_review + filter: "data.action == 'submitted'" + + - # On PR sync, remove all waiting labels. + name: pull_request + connection: github + event_type: pull_request + call: handlers.py:on_pull_request_sync + filter: "data.action == 'synchronize'" diff --git a/github_wait/handlers.py b/github_wait/handlers.py new file mode 100644 index 00000000..e6b51464 --- /dev/null +++ b/github_wait/handlers.py @@ -0,0 +1,140 @@ +"""Handlers for github issue bot""" + +from github import Issue +from github import PullRequest + +from autokitteh import Event +from autokitteh.github import github_client + + +github = github_client("github") + + +# waiting only for push. +_waiting_push_label = "waiting" + +# waiting for either push or comment. +_waiting_any_label = "waiting:any" + +_IssueOrPR = Issue.Issue | PullRequest.PullRequest + + +# Can be called on either issue comments, pr review comments, or pr review submitted. +def on_command(event: Event) -> None: + match event.data.command.name: + case "wait": + add, remove = _waiting_push_label, _waiting_any_label + case "wait-any": + add, remove = _waiting_any_label, _waiting_push_label + case _: + print("unhandled command") + return + + data = event.data.actual_data + + repo = github.get_repo(data.repository.full_name) + + if data.get("issue"): + issue: _IssueOrPR = repo.get_issue(number=data.issue.number) + elif data.get("pull_request"): + issue = repo.get_pull(number=data.pull_request.number) + + _label(issue, adds=[add], removes=[remove]) + + if data.get("comment"): + issue.get_comment(data.comment.id).create_reaction("+1") + + +def on_pull_request_sync(event: Event) -> None: + data = event.data + + repo = github.get_repo(data.repository.full_name) + pr = repo.get_pull(data.number) + + _label(pr, adds=[], removes=[_waiting_push_label, _waiting_any_label]) + + +def on_issue_comment(event: Event) -> None: + data = event.data + + if not any( + label["name"] in [_waiting_any_label, _waiting_push_label] + for label in data.issue.labels + ): + print("not waiting, ignored") + return + + if data.slash_commands and any( + cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands + ): + # Don't remove labels if this comment is a command. + print("comment contains wait command, ignored") + return + + repo = github.get_repo(data.repository.full_name) + issue = repo.get_issue(data.issue.number) + + # Remove only wait-any label on new comment. + _label(issue, adds=[], removes=[_waiting_any_label]) + + +def on_pull_request_review_comment(event: Event) -> None: + data = event.data + + if not any( + label["name"] in [_waiting_any_label, _waiting_push_label] + for label in data.pull_request.labels + ): + print("not waiting, ignored") + return + + if data.slash_commands and any( + cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands + ): + # Don't remove labels if this comment is a command. + print("comment contains wait command, ignored") + return + + repo = github.get_repo(data.repository.full_name) + pr = repo.get_pull(data.pull_request.number) + + # Remove only wait-any label on new comment. + _label(pr, adds=[], removes=[_waiting_any_label]) + + +def on_pull_request_review(event: Event) -> None: + data = event.data + + if not any( + label["name"] in [_waiting_any_label, _waiting_push_label] + for label in data.pull_request.labels + ): + print("not waiting, ignored") + return + + if data.slash_commands and any( + cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands + ): + # Don't remove labels if this comment is a command. + print("comment contains wait command, ignored") + return + + repo = github.get_repo(data.repository.full_name) + pr = repo.get_pull(data.pull_request.number) + + # Remove only wait-any label on new comment. + _label(pr, adds=[], removes=[_waiting_any_label]) + + +def _label(issue: _IssueOrPR, adds: list[str], removes: list[str]) -> None: + labels = {label.name for label in issue.get_labels()} + + for remove in removes: + if remove in labels: + print(f"removing label {remove}") + issue.remove_from_labels(remove) + + for add in adds: + if add not in labels: + print(f"adding label {add}") + issue.add_to_labels(add) From d49135fab773b0d698e64253c6a1434840fb96ff Mon Sep 17 00:00:00 2001 From: Itay Donanhirsh Date: Mon, 8 Dec 2025 19:54:58 -0800 Subject: [PATCH 2/2] work --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 65b696fe..7ab90f65 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ demonstrate basic system features, integration APIs, and best practices. | [Cancel GitHub Copilot access for inactive users](./github_copilot_seats/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=github_copilot_seats) | If Copilot was not used in a preceding period by users, unsubscribe and notify them in Slack. Users can ask for their subscription to be reinstated. | github, slack | | [GitHub Marketplace to Slack](./github_marketplace_to_slack/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=github_marketplace_to_slack) | Forward GitHub Marketplace notifications to Slack | github, slack | | [GitHub Review - PR Review Request Bot](./github_review/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=github_review) | Slash command bot for requesting and removing PR reviews via comments | github | +| [GitHub Wait - PR/Issue State Management Bot](./github_wait/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=github_wait) | Slash command bot for managing waiting states on PRs and issues with automatic label updates | github | | [Gocat - URL Shortener & Redirector](./gocat/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=gocat) | Simple URL shortener using Google Sheets as a data store with webhook-based redirection | googlesheets | | [Google Calendar To Asana](./google_cal_to_asana/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=google_cal_to_asana) | Creates Asana tasks based on Google Calendar events | googlecalendar, asana | | [Create Jira ticket from Google form](./google_forms_to_jira/)
[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?template-name=google_forms_to_jira) | Create and update Jira tickets automatically from Google Forms responses | googleforms, jira |