slop-review

What comes back

Prose by default: a review is read more often than it is parsed — by the person who wrote it and by the agent it is for, which reads prose as readily as anything else and pays fewer tokens for it. --format json prints the same review as structure, for the program that parses it:

{
  "decision": "request_changes",
  "summary": "The rename parser is the only part I'd hold up.",
  "vcs": "jj",
  "revset": "trunk()..@",
  "repo_root": "/home/you/projects/slop-review",
  "comments": [
    {
      "id": "c1",
      "commits": [
        {
          "id": "rlrmnkssuyyv",
          "short_id": "6b2964ad",
          "subject": "Read commits without binding to a VCS"
        }
      ],
      "location": {
        "path": "crates/core/src/vcs.rs",
        "lines": {
          "side": "new",
          "start": 287,
          "end": 291,
          "quoted": "    match (spec.find('{'), spec.find('}')) {"
        }
      },
      "body": "This assumes the brace form. jj also emits a bare `a => b`.",
      "first_sent": null
    },
    {
      "id": "c2",
      "commits": [{ "id": "kwpoztqyzznm", "short_id": "9c1f0b32", "subject": "Add .envrc" }],
      "location": null,
      "body": "Fix this up into the commit that added the file.",
      "first_sent": "2026-09-17T16:13:05Z"
    }
  ]
}

Every field is there so the agent can act without going back to the VCS: a commit carries its subject, not just an ID, and quoted echoes the reviewed source so the annotation can be found without re-reading a diff it never received. side matters — new line numbers are valid in the file as of the last commit in commits, old ones belong to before the first, so quoted is the only reliable anchor for those.

commits has more than one entry when the reviewer was looking at an accumulated range; the comment is then about the combined change, and line numbers are relative to the parent of the first commit.

The two nullable anchors say how wide the remark is. location is null for one about the commits themselves; location.lines is null for one about a whole file. summary is null rather than "" when the reviewer left it blank.

The prose that --format md prints — the default — carries the same content, and is cheaper to read and harder to machine-handle. Pick per agent.

Remarks sent before

first_sent is when a remark first reached an agent, as a UTC timestamp of the form YYYY-MM-DDTHH:MM:SSZ, and null for one going out for the first time. A remark is kept against the commits it was written over and comes back for as long as they are unchanged — see progress — so a second review of a stack the agent only partly rewrote carries remarks the agent has already read. They are sent again rather than dropped: a second review with two standing remarks and nothing new would otherwise look like a clean approval, and the reviewer saw them come back in the window and chose to let them stand. But a reader that acts on each remark once — a script that files them somewhere, a log of what was said — needs to tell the two apart, and this field is how.

Three things pin its meaning down. It is set once the review has left the process, printed or written to the -o file, so a run that failed on the way out has sent nothing. It is the first sending: a third review of the same remark carries the same date as the second. And editing a remark's wording clears it, because new words are a new remark to whoever reads them; the id stays. The prose rendering says the same thing as a line under the summary counting them and an italic Already sent on … line on each.