wh explain
Reads a diff, trims it, and streams a plain-english answer. One command, three modes, the same spec as the web.
- The diff is preprocessed per
shared/prompts/: lockfiles, vendored paths, minified files, generated files (source maps), and binaries are dropped; big diffs are capped at 400 lines per file and 4000 in total - The answer streams; section labels are amber
- The closing line is elapsed time and model, plus
N in · N outwhen the provider reports token usage
modes
| mode | asks for | sections |
|---|---|---|
| (default) | a review | summary, watch out |
--changelog | release notes, one line per user-visible change | added, changed, fixed, removed (empty ones left out) |
--describe | a pull request to paste | title (in the repo's own subject style), description, testing when the diff shows how to verify |
--changelog and --describe exclude each other (exit 2).
ranges
A typed range goes to git diff verbatim; only the commit log drops the third dot.
--uncommitted is git diff HEAD: staged and unstaged together, the change you are about to commit. It takes no range, and the payload carries no commits: block because there are no commits yet. Untracked files are not in a diff until you git add them. Anything after -- is a git pathspec, passed to the diff and to the commit list alike.
--describe
With no range, --describe compares against the default branch from their merge base (<default>...HEAD), so a base that moved on never leaks into the draft, and tells the model the branch names in a context: branch feat/auth into main block. A bare ref there means <ref>...HEAD. Nothing is written to github.
follow-ups
--chat keeps the conversation open after the answer: a ? prompt takes further questions about the same diff, grounded in what the model has already seen, so a second question costs no second diff.
- An empty line or ctrl-d ends it
- There is no history beyond what the terminal gives a line of input
- It needs a terminal on both ends and quietly stays a one-shot when either is a pipe
- It cannot be combined with
--dry-run(exit 2)
The same flag works on wh why.
output
- When stdout is not a terminal, the two muted status lines go to stderr, so
> body.mdholds only the answer --dry-runprints the exact payload and makes no call- An empty range:
nothing to explain in <range>, ornothing uncommitted to explainwith--uncommitted - A pathspec that matches nothing names it:
nothing to explain in HEAD~5.. under src/ - No default branch to compare against:
cannot determine default branch, pass a range like main..
Provider and network failures have their own page: when a call fails.