Build in the review tool.

This commit is contained in:
Greyson Parrelli
2026-08-24 09:24:01 -04:00
parent 4eef6a28d3
commit 407dd81512
55 changed files with 12932 additions and 40 deletions
+119
View File
@@ -0,0 +1,119 @@
---
name: address-review
description: Fetch the code-review comments left in this playpen tab's review pane, address each one (edit code + post an inline reply), and resolve the thread. Use when the user says "address the review", "check the review", "any review comments?", or runs /address-review.
---
# Address the review in this tab
Playpen serves a GitHub-style review UI for the repository the current tab is
working in. The user leaves line-level comments in the review pane and clicks
**Submit review**; this skill pulls those comments, addresses them in code, and
replies inline so the responses appear live in the pane — no copy-pasting from
the terminal.
## 1. Find the review
Every terminal pane in playpen is handed its own tab's review endpoint:
```bash
BASE="$PLAYPEN_REVIEW_URL" # e.g. http://127.0.0.1:8420/t/tab3
```
That variable is the whole of the addressing. It names **this tab's** review, so
there is no repository to pass and no way to address comments meant for another
worktree.
If it is empty — you are running outside playpen, or in a shell started before
the server came up — discover it instead:
```bash
curl -s http://127.0.0.1:8420/api/tabs
```
That lists every tab with the `path` it is reviewing. Match `path` against
`git rev-parse --show-toplevel` and build the URL as
`http://127.0.0.1:8420/t/<id>`. Do **not** guess: reviewing the wrong tab means
addressing another branch's comments. If nothing matches, or the connection is
refused, tell the user to open a review pane in this tab (**Ctrl+Shift+D**) and
stop.
Confirm the review is open:
```bash
curl -s "$BASE/api/repo"
```
- `{"open":true,…}` — good, go on.
- `{"open":false}` or a 409 — this tab has no review pane. Ask the user to open
one and stop.
## 2. Fetch the pending comments
```bash
curl -s "$BASE/api/review/pending"
```
Returns this review's submitted, unresolved comments. Each has:
- `id` — use this to reply and resolve
- `level` — how it's anchored:
- `line``file` + `side` (`new`/`old`) + `line`..`endLine` (a line or range)
- `file``file` only (a comment about the whole file)
- `review` → not tied to anything (a comment about the overall change set)
- `file`, `side`, `line`, `endLine` — the anchor, per `level` above
- `body` — what the reviewer wants
- `author``user` for the reviewer's own comment, `claude` for one left by a
review you ran yourself (the `leave-review` skill). Both are real work and both
are addressed the same way; just say which is which in your summary. If the
user asked specifically for *their* comments, filter to `author: "user"`.
- `replies` — any prior back-and-forth on the thread
Treat each level appropriately: for `line` address the specific lines or range;
for `file` consider the file as a whole; for `review` weigh it against the entire
change set.
If the array is empty, say there's nothing to address and stop.
## 3. Address each comment
For every pending comment, in order:
1. **Read the context.** Open `file` around `line` (on the given `side`) so you
understand what the reviewer is pointing at.
2. **Decide the response type:**
- **Change request** → make the edit with your normal file-editing tools.
- **Question / discussion** → don't necessarily edit; answer in the reply.
- **Unclear** → ask a clarifying question in the reply and leave the thread
open (skip the resolve step).
3. **Post an inline reply** describing exactly what you did (or your answer):
```bash
curl -s -X POST "$BASE/api/comments/<id>/replies" \
-H 'Content-Type: application/json' \
-d '{"body":"Done — main() now logs and returns the error instead of printing.","author":"claude"}'
```
4. **Resolve the thread** once it's fully handled (skip if you asked a question):
```bash
curl -s -X POST "$BASE/api/comments/<id>/resolve"
```
Replies and resolutions appear in the review pane immediately over its live
connection.
## 4. Summarize
Report back: which comments you addressed and how, which you left open (and why),
and any code changes you made. Do **not** commit unless the user asks. If your
edits changed line numbers, mention that the reviewer may want to hit
**↻ Refresh diff** in the pane to re-anchor against the new code.
## Notes
- Keep replies concise and specific — they're read inside a comment thread.
- Comment ids are unique per review. A `404` from a reply or resolve usually
means `$PLAYPEN_REVIEW_URL` points at a different tab than you think.
- Anchoring is by line number in the diff at comment time; after your edits the
original line may have moved. That's expected — the reply plus resolve keeps
each round coherent, and the user refreshes the diff for the next round.
+234
View File
@@ -0,0 +1,234 @@
---
name: leave-review
description: Review the diff open in this playpen tab's review pane and leave your own line-level comments there (as Claude), instead of printing a terminal report. Can delegate the analysis to another review skill — pr-review, security-review, or any project-specific reviewer — and post its findings as inline comments. Use when the user says "review the diff", "leave a review", "review my changes in the pane", "review this with <skill>", or runs /leave-review.
---
# Leave a review in this tab's review pane
Playpen serves a GitHub-style review UI for the repository the current tab is
working in. Normally the user leaves comments there and an agent addresses them —
the `address-review` skill. This skill runs the loop the other way: **you** review
the diff on screen and leave the comments, anchored to real lines, so the user
reads them in the same pane they'd read a colleague's review in — reply, resolve,
or hand them back to be fixed.
**This skill reviews. It does not fix.** Do not edit a single file while running
it, even for something trivial or obviously right — a review whose findings have
already been silently applied is impossible to read. Fixing is `address-review`'s
job, and your comments land in its queue automatically (see step 7).
## 1. Set the target
```bash
BASE="$PLAYPEN_REVIEW_URL" # e.g. http://127.0.0.1:8420/t/tab3
curl -s "$BASE/api/repo"
```
`$PLAYPEN_REVIEW_URL` is exported into every terminal pane and names **this
tab's** review, so there is nothing to choose and no way to leave your review on
someone else's branch.
- `{"open":true,…}` with a `context` object — good, go to step 2.
- `{"open":false}` or a 409 — this tab has no review pane. Tell the user to open
one (**Ctrl+Shift+D**) and stop. Don't review a different tab.
- `$PLAYPEN_REVIEW_URL` empty, or connection refused — list the tabs with
`curl -s http://127.0.0.1:8420/api/tabs` and match a tab's `path` against
`git rev-parse --show-toplevel`. If nothing matches, stop and say so.
## 2. Find out which diff to review
The base ref, the **uncommitted** toggle, and any single commit picked out of the
range are chosen in the pane, and the pane publishes that selection to the
server. It's the `context` field from step 1:
```json
"context": { "base": "main", "uncommitted": true, "commit": "" }
```
Review **that** diff. It's what the user is looking at, and it's the only diff
whose line numbers the pane can place a comment on.
- `"context": null` — nothing has been on screen yet. Ask the user to open the
review pane, then re-read it. Don't guess a base.
- A non-empty `"commit"` means they're reading one commit of the range on its
own. Review that commit; the numbers only mean anything there.
- If the user asks for a different diff ("review just the last commit"), you can
pass your own `base`/`uncommitted`/`commit` — but say plainly that those
comments will only appear once they switch the pane to match.
## 3. Get the diff
```bash
curl -s "$BASE/api/diff?base=main&uncommitted=true"
```
The `patch` field is the exact bytes the pane renders, and `files` is the
per-file summary — read that first, for scale. `commits` lists the commits the
range spans, so a user asking about "the second commit" can be answered by
re-fetching with `&commit=<sha>`.
Use this rather than running `git diff` yourself: identical bytes means identical
line numbers, which is what makes an anchor land.
A very large change set comes back with `"oversized": true` and no patch. Add
`&force=1` to get it anyway, or narrow to one commit.
Read the surrounding code with your normal tools — the patch alone is rarely
enough to tell a real bug from a fine one. If the patch is empty, say so and stop.
## 4. Pick the lens — and delegate when there's a skill for it
This skill is the delivery mechanism; the *judgment* can come from a skill that
already knows this codebase. Before reviewing anything yourself, check the skills
available to you for one that fits this repo or what the user asked for:
- **The user named one** — "review this with pr-review", "do a security review",
"/leave-review pr-review". Invoke it with the Skill tool. Their choice wins.
- **A project-specific reviewer exists** for this repo (e.g. `pr-review` for
Signal-Android conventions) — prefer it over your own generic pass, and say
which one you used.
- **Several apply** — run them in turn (conventions pass, then security pass) and
merge the findings, dropping duplicates.
- **None fits** — review it yourself against the rubric below.
When you invoke a review skill, follow its instructions as written, but note up
front that its findings are going to be posted as inline comments rather than
printed — so you need, for each finding, a **file path and a line number in this
diff**, plus the rule name it fired. Then continue at step 5 with its report as
your finding list. If it produces a terminal report anyway, that's fine: its
numbered findings are exactly the input you need.
Two limits:
- **Only delegate to skills that review.** Some skills change code as part of
their job (`simplify`, for instance, applies its own fixes). Don't invoke one of
those here — it would edit the diff out from under the review. If the user asked
for one by name, say why you're not running it and offer its rubric as a lens
instead.
- **If a delegate doesn't apply** (a Signal-Android reviewer in an unrelated repo,
say) don't force it. Fall back to your own pass and mention the swap.
### Rubric for your own pass
Restrict findings to **added and modified lines** — don't review code the diff
didn't touch, unless the change made it newly wrong. In rough priority order:
1. **Correctness** — logic that doesn't do what the code around it clearly
intends; off-by-one, inverted condition, wrong variable, missed case.
2. **Error handling** — swallowed errors, unchecked returns, an error path that
leaves state half-updated, panics on input the caller controls.
3. **Resources and lifetimes** — leaks, missing close/cancel, work that outlives
what it belongs to.
4. **Concurrency** — data races, state mutated without the lock its neighbours
take, deadlock ordering.
5. **Interface and contract** — a caller that can now be silently wrong; a
behaviour change not reflected in the doc comment right above it.
6. **Tests** — a new branch with real failure modes and no test; a test that
would pass with the bug still in.
7. **Fit** — code that ignores an existing helper, layering, or naming pattern
the file establishes.
8. **Cruft** — commented-out code, narration comments, debug logging, a stray
TODO with no owner.
## 5. Turn findings into comments
Choose the anchor per finding, most specific that will actually render:
| Finding | Anchor |
| --- | --- |
| A specific line | `"level":"line"`, `file`, `line` |
| A construct spanning lines | add `endLine` |
| A deleted line (the change removed something needed) | add `"side":"old"` |
| The file as a whole, or a line not in the patch | `"level":"file"`, `file` |
| Cross-cutting: architecture, a missing test file, the change set as a whole | `"level":"review"` |
**Verify every line number against the patch before you post it.** Read the hunk
header — `@@ -old,n +new,m @@` — and count: the number you pass must be a line the
patch actually shows on that side (an added `+` line or a context line). A comment
on a line the pane doesn't render is invisible; when in doubt, widen to a range or
drop to a file-level comment. Never post a line number you inferred from your
memory of the file.
Three details that decide whether a comment lands where you meant:
- `file` takes the path as the diff names it: the **new** path, or the old one
for a deleted file.
- A range hangs its thread off the **end** line, GitHub-style. Put the range
around the construct and let it anchor at the bottom.
- Prefer `"side":"new"`. Old-side line numbers are positions in the base
revision, so they stop meaning anything the moment the user changes the base
ref, and the comment goes outdated. Only use `old` when the finding really is
about a line the change deleted.
**Don't repeat what's already been said.** Fetch the existing threads first:
```bash
curl -s "$BASE/api/comments"
```
Skip anything the user already raised, and anything **you** raised on an earlier
pass — including threads they resolved. Re-posting a resolved finding is the
fastest way to make this skill not worth running twice.
Write each body like a comment in a thread someone has to read:
- Lead with what's wrong, in one sentence. Then why it matters, then the fix.
- 24 sentences. Show the fix as code when that's shorter than describing it.
- Open with a severity label so the rail can be triaged: `Blocking:`,
`Should fix:`, `Nit:`, or `Question:`.
- When a delegate skill produced it, name the rule at the end in parentheses —
e.g. `(pr-review: LogTagInlined)` — so the user can trace it.
- **Bodies render as plain text, not markdown.** Newlines and indentation are
preserved, so an indented line or two is how you show suggested code. Backticks
around an identifier read fine; `**bold**` and ``` fences just show up as
punctuation, so skip them.
- No praise-only comments, no restating what the diff does, no "consider possibly
maybe". One issue per comment; group unrelated nits in one file-level comment
rather than five line comments.
- Aim for **at most ~1012 comments**. Past that, keep the serious ones and roll
the tail into a single review-level comment. A wall of comments reads as noise
and buries the two that mattered.
## 6. Post them
```bash
curl -s -X POST "$BASE/api/comments" -H 'Content-Type: application/json' -d '{
"level":"line","file":"src/review/Store.zig","side":"new","line":84,"endLine":91,
"author":"claude",
"body":"Should fix: save() runs while the write lock is held, so a slow disk blocks every reader for the length of the write. Snapshot the list under the lock and write outside it."
}'
```
- `base`, `uncommitted` and `commit` default to the selection the pane published,
so leave them out unless you deliberately reviewed another diff.
- `"author":"claude"` is the point: the comment shows up as **Claude**'s in the
pane, and lands as an **open thread** rather than a draft of the user's. Drafts
are theirs alone — never post as `"author":"user"`.
- Post the most serious findings first — the comments rail is ordered by creation.
- Finish with **one review-level comment** as the review body: what you reviewed
(base ref, file count), which lens you used, and the two or three themes worth
the user's attention. Without it they can't tell what was checked versus what
came up clean.
Everything appears in the pane immediately over its live connection.
## 7. Summarize in the terminal
Report: the lens used, how many comments you left and where (`file:line — one-line
summary` each), and anything you deliberately didn't comment on. Then tell them
the two ways forward:
- Read them in the pane — reply, or resolve the ones they disagree with.
- Or say **"address the review"** — your comments are open threads, so they're in
the same queue the user's comments go into, and `address-review` will fix them
the same way. Mention this: it's the whole loop, and it's not obvious that your
own findings come back to you as work.
## Notes
- Your comments count toward the review's **open** count, the same as the user's —
it means "threads awaiting someone", not "awaiting Claude".
- If the user edits code after you review, your anchors drift. That's expected;
they hit **↻ Refresh diff** and the next pass re-anchors.
- Reviewing a large diff: work file group by file group and post as you go, so a
long pass still leaves usable comments if it's interrupted.