Metadata-Version: 2.4
Name: twgh
Version: 0.6.0
Summary: Read-only GitHub reviewer CLI: triage, re-review diffs, comment threads
Author-email: sysid <sysid@gmx.de>
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.15.1
Requires-Dist: rich>=13.0.0

# 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`, or `COMMENTED`
- **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:

```sh
# 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):

```toml
[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:

```sh
twgh status --author "dependabot[bot]"
```


### Prerequisites

- The GitHub CLI [`gh`](https://cli.github.com), 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?

```sh
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?

```sh
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

```sh
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?

```sh
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 with `twgh 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?

```sh
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

```sh
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

```sh
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

```sh
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

```sh
make test              # pytest + coverage (floor 85%)
make static-analysis   # ruff lint-fix, format, ty
```
