Read-only GitHub reviewer CLI: triage, re-review diffs, comment threads
Project description
twgh
Read-only GitHub reviewer CLI — for humans and agents.
Triage what needs you, re-review only what changed since you last looked (force-push robust), and see which of your comments were addressed. Works across github.com and GitHub Enterprise.
twgh never writes to GitHub — no approvals, no comments, no merges. It is the read side of reviewing; for write actions it hands you off to the browser.
Every command produces clean, pipeable output: --json where the data is
tabular, unified diff hunks where the data is code. No wrapper needed —
twgh status --json | jq and twgh diff 42 | llm "summarise" work
out of the box. This makes twgh equally useful as a human terminal tool
and as a building block in agent and CI pipelines.
GitHub concepts
Understanding these GitHub primitives helps clarify what twgh surfaces and why.
Comment types on a PR
┌─────────────────────────────────────────────────────────────┐
│ GitHub PR Comments │
├──────────────────────────┬──────────────────────────────────┤
│ Issue Comments │ Review Threads │
│ (PR-level / timeline) │ (inline / code-level) │
├──────────────────────────┼──────────────────────────────────┤
│ • Not tied to a file │ • Anchored to a file + line │
│ • No commit reference │ • Tied to a specific commit OID │
│ • Cannot be "resolved" │ • Can be resolved/unresolved │
│ • Cannot be "outdated" │ • Can become outdated when the │
│ • General conversation │ underlying code changes │
│ on the PR timeline │ • Groups into a thread (replies)│
└──────────────────────────┴──────────────────────────────────┘
| Level | What it is | GitHub API source | In twgh |
|---|---|---|---|
| Issue comment | Free-form message on the PR timeline ("LGTM!", "please rebase") | pr.comments.nodes |
comments command only |
| Review thread | A conversation anchored to a specific line of code, started during a review | pr.reviewThreads.nodes |
threads command (one row per thread) |
| Review comment | An individual reply within a review thread | comments inside a thread node | comments command (one row per comment) |
A review itself (the APPROVE / REQUEST_CHANGES / COMMENT submission) is a
separate entity — twgh uses it for anchor calculation but does not surface its
body in either threads or comments.
Reviews and review state
When you submit a review on GitHub, it records:
- State:
APPROVED,CHANGES_REQUESTED, orCOMMENTED - Commit OID: the HEAD commit at the time you submitted
twgh uses this commit OID as the anchor — the baseline for twgh diff.
Everything between anchor and current HEAD is "what changed since you last
looked". Because the anchor is a commit hash (not a branch pointer), it
survives force-pushes and rebases.
Resolved vs outdated threads
- Resolved: someone explicitly clicked "Resolve conversation". The feedback loop is considered closed.
- Outdated: the code the comment points at has moved (the diff hunk no longer exists at that position). GitHub marks the thread but does not auto-resolve it.
twgh's classify_thread maps these states plus the commit history into a
verdict: ✓ resolved, ~ changed (code moved but not resolved), or
· untouched (nothing happened).
Force-push safety
Many review tools break when authors force-push because branch history is
rewritten. twgh avoids this by anchoring on commit OIDs returned by the
GraphQL API — these point at the original commits your review was filed
against. GitHub's compare endpoint (/compare/<oid>..<oid>) works with any
reachable object, including pre-rebase commits that are no longer on any branch.
PR references
<ref> is a PR URL, host/owner/repo#N, owner/repo#N, owner/repo/N, or a
bare number inside a repo checkout. status prints URLs — paste them straight
back.
Host selection
-H/--gh-host overrides $GH_HOST, which defaults to github.com. Works as a
global option (twgh -H ghe.example.com status) or per command.
Installation, Configuration
Optional TOML config at ~/.config/twgh/config.toml (override with --config).
Bootstrap a starter with:
# Installlation
uv tool install twgh # or: pipx install twgh
make install # uv tool install -e . + bash completion
# Configuration
twgh edit --init
Hide PRs from bots/service accounts in status and threads (inbox mode):
[exclude]
authors = ["dependabot[bot]", "self-hosted-renovate[bot]"]
# Per-host additions (extends the global list):
[exclude."github.enterprise.com"]
authors = ["renovate-enterprise[bot]"]
Override the exclusion list to show a specific author:
twgh status --author "dependabot[bot]"
Prerequisites
- The GitHub CLI
gh, authenticated for each host you review on:gh auth login --hostname <host>. twgh borrows gh for auth, host routing, and transport; it never handles your token.
Commands
twgh status — which PRs need me right now?
twgh status
twgh status --json
twgh status --author "dependabot[bot]"
Cross-repo rollup of every open PR you are review-requested on, commented on,
or have reviewed. Per PR: author, your review state against the current head
(needs_review / needs_re_review / up_to_date), and open-thread count.
★ marks PRs that moved since your last status run — new head or new comments,
tracked via a local snapshot (the first run deliberately flags nothing).
| Option | Description |
|---|---|
--json |
Machine-readable JSON array — one object per PR |
--author <user> |
Show only PRs by this author (overrides exclusion list) |
twgh diff — what changed since I last looked?
twgh diff <ref>
twgh diff <ref> --all
twgh diff <ref> --comments
twgh diff <ref> --web
The author pushed again; you don't want to re-read the whole PR. diff anchors
on your last submitted review (else your last inline comment) and shows
anchor→HEAD, restricted to the files you commented on. The anchor is a commit
OID, so the diff survives force-pushes and rebases. With no prior review there
is nothing to re-diff — it points you at twgh open instead.
Output is unified diff text, paged through delta / $PAGER / less -R.
When stdout is not a TTY (piped or redirected), the pager is skipped and raw
diff text goes to stdout — ready for grep, patch, or an LLM.
| Option | Description |
|---|---|
--all |
Show the whole-PR diff, not just files you commented on |
--comments / -C |
Weave your inline review comments into the diff as # -prefixed context above each file's patch |
--web |
Open the anchor→HEAD range in GitHub's compare view instead of the pager. A force-push-safe deep-link the web UI can't construct by hand. File-scoping and --all do not apply |
twgh compare — diff between two arbitrary commits
twgh compare <base> <head>
twgh compare <base> <head> --web
twgh compare abc123 def456 --repo LOS/my-repo
Show the diff between any two commits. Both short and long SHA forms are
accepted — GitHub's API and web UI resolve them server-side. Repo context
comes from the current directory's git remote; use --repo to override.
Same output behaviour as diff: paged for humans, raw text when piped.
| Option | Description |
|---|---|
--web |
Open the compare view in the GitHub web UI (/compare/<base>..<head>) |
--repo / -R <owner/repo> |
Repository override — use instead of CWD git context |
twgh threads — were my comments addressed?
twgh threads <ref>
twgh threads # global inbox (no ref)
twgh threads <ref> --mine
twgh threads <ref> --by alice
twgh threads <ref> --all
An action-oriented triage view: one row per review conversation with a verdict showing whether it still needs attention:
✓ resolved— the thread is marked resolved~ changed— the anchored code moved since the comment (inspect withtwgh diff)· untouched— the point still stands as written
Omit <ref> for a global inbox of open threads across all PRs that involve
you.
| Option | Description |
|---|---|
--mine |
Only threads you opened |
--by <user> |
Filter by thread author |
--all |
Include resolved threads |
twgh comments — what was said while I was away?
twgh comments <ref>
twgh comments <ref> --mine
twgh comments <ref> --by carol
twgh comments <ref> --all
A chronological reading view of everything said on a PR: top-level comments and inline review comments, sorted by time. Think of it as a chat log — you read top to bottom to understand the full conversation timeline. Comments in resolved threads are hidden by default.
| Option | Description |
|---|---|
--mine |
Only your comments |
--by <user> |
Filter by comment author |
--all |
Include inline comments in resolved threads |
threads vs comments
Both draw from the same PR data but serve different workflows:
threads |
comments |
|
|---|---|---|
| Unit | one row per conversation | one row per comment |
| Order | by file path | by time |
| Key info | actionable verdict | full message body |
| Question answered | "what needs my response?" | "what was said?" |
| Scope | single PR or global inbox | single PR only |
twgh open — hand off to the browser
twgh open <ref>
Opens the PR in the browser for the write actions twgh stays out of: approving, replying, resolving, merging.
twgh edit — configure twgh
twgh edit --init # create starter config and open in $EDITOR
twgh edit # open existing config in $EDITOR
Opens the config file in $EDITOR. Use --init to bootstrap a starter config
if one doesn't exist yet.
twgh summary — reviewer activity report
twgh summary # your reviews, last 30 days
twgh summary --days 7 # last week only
twgh summary --reviewer alice # someone else's activity
twgh summary --json # machine-readable output
Produces a three-tier report measuring a reviewer's effort, engagement depth, and responsiveness over a time window.
Volume metrics
| Metric | What it measures |
|---|---|
| PRs reviewed | Unique PRs where the reviewer submitted at least one review |
| Lines reviewed | Sum of additions + deletions across all reviewed PRs (proxy for review scope) |
| Files reviewed | Sum of changed files across all reviewed PRs |
| Total comments | Inline review comments authored by the reviewer across all threads |
| Comments / PR | Average comments per PR — measures thoroughness |
| Unique repos | How many distinct repositories the reviewer covers — breadth of impact |
Engagement metrics
Each time a reviewer submits a review on GitHub, it carries one of three states:
| State | Meaning |
|---|---|
| Approved | Reviewer signed off — could range from rubber-stamp to thorough |
| Changes requested | Reviewer took a position and authored specific feedback |
| Comment only | Reviewer engaged but did not commit to a verdict |
A single PR can accumulate multiple reviews (e.g., request changes → author pushes fixes → re-review and approve = 2 reviews on 1 PR). This is why "total reviews" can exceed "PRs reviewed".
Quality signals
Engagement score — a weighted average that rewards deeper review effort:
score = (changes_requested × 3 + comment_only × 2 + approvals × 1)
─────────────────────────────────────────────────────────────────
total_reviews
| Score | Interpretation |
|---|---|
| 1.0 | All approvals — potential rubber-stamping |
| 1.5 | Mix of approvals and substantive comments |
| 2.0 | Balanced engagement with meaningful discussion |
| 2.5–3.0 | Heavy change-request activity — very hands-on reviewer |
The weights reflect effort: requesting changes requires the reviewer to articulate specific problems and defend a position, while approving can be as simple as clicking a button.
Median turnaround — median time from PR creation to the reviewer's first review submission on that PR. Measures responsiveness — how long authors wait for initial feedback. This is a rough proxy: it penalises the reviewer for PRs created before they were assigned, or PRs they picked up late from a queue.
Output conventions
twgh uses two output formats, chosen by what the data naturally is:
| Data shape | Format | Commands |
|---|---|---|
| Tabular (PR list, thread verdicts, metrics) | Rich table (human) or --json (machine) |
status, threads, comments, summary |
| Code delta | Unified diff text with @@ hunks |
diff, compare |
Diff output is already structured — @@ markers delimit hunks, +/- prefix
changed lines, file headers name each path. Any downstream tool, LLM, or agent
can parse it without a JSON wrapper. Piping works naturally: the pager is
skipped when stdout is not a TTY.
Agent skill
The skill/ directory contains twgh-review-check, a ready-made agent skill
(for GitHub Copilot CLI, Claude Code, or any compatible harness) that
orchestrates twgh to answer: "Can I approve this PR, or do we need another
round?"
The skill runs twgh threads, twgh diff --comments, and twgh comments,
then synthesises a per-thread assessment table with a final APPROVE or
DISCUSS verdict. It handles the nuances: ✓ resolved threads are done,
~ changed threads need the diff inspected to distinguish real fixes from
coincidental churn, and · untouched threads need the comment stream checked
for author replies that GitHub didn't mark as resolutions.
Install it by copying skill/ to your agent's skill directory, or point your
agent at it directly. The skill has no dependencies beyond twgh and gh.
Development
make test # pytest + coverage (floor 85%)
make static-analysis # ruff lint-fix, format, ty
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file twgh-0.6.2.tar.gz.
File metadata
- Download URL: twgh-0.6.2.tar.gz
- Upload date:
- Size: 35.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6742314d2373617d03e8871dbbef220a94ce0b3a820b26d0136112d23c40c3dd
|
|
| MD5 |
4077ec1d0ec25a7eed38e5755556afdf
|
|
| BLAKE2b-256 |
0cb05760c1789b6ac2126c57a21e2f30b5f0116f5b3322c4c7a3b852d57118a4
|
File details
Details for the file twgh-0.6.2-py3-none-any.whl.
File metadata
- Download URL: twgh-0.6.2-py3-none-any.whl
- Upload date:
- Size: 33.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
177e3e8d97805690070465ba925ed2a83d02b115fc2a7e1d0c623392a9a0ce5f
|
|
| MD5 |
1aff1fb024f5b085d44ca6aaf71feec4
|
|
| BLAKE2b-256 |
1c1328c5eadeb2cc9a5ca9da4086237065c87b5ecf0b58349318a9fee4564251
|