<p><em>On the silence after the spinner stops — and why the review of machine-written code belongs where the code already is.</em></p><p>There's a particular silence that falls when an agent session ends.</p><p>The spinner stops. The terminal settles. And there it is: 50 files changed. Migrations you didn't write. A middleware you don't remember asking for. Three tests that are either brilliant or nonsense.</p><p>You scroll up. You scroll back down. And then comes the question that e 0.9.7 and 0.9.8 are entirely about:</p><p>Now what?</p><h2>The honest answer used to be "push it and hope"</h2><p>For a while, our answer was the same as everyone else's: commit it, push it, open a PR, and read the diff on GitHub.</p><p>It works. It's also, if we're being honest, a slightly absurd ritual. Think about what you're actually doing: taking code that's sitting right there on your disk, uploading it to a website, and then reading it in a text viewer that can't run it, can't test it, can't jump to a definition, and can't undo anything.</p><p>And the review you need here isn't the review a PR is designed for. A normal PR review asks "is this how I'd have written it?" But you didn't write this. Nitpicking the brace style of a machine is a waste of a good afternoon. What you actually need is much blunter:</p><ul><li><p>What did it touch? (And which of those things scare me?)</p></li><li><p>Is any of it obviously wrong?</p></li><li><p>Does it still work?</p></li><li><p>Can I take it back?</p></li></ul><p>Four questions. None of them need a browser. All of them are easier in the editor, where the code already is.</p><p>So we built that instead.</p><h2>0.9.7: the review, where the code lives</h2><p>Press ⌘⌥V after a session and e shows you the whole changeset in one place.</p><p>The first thing it does is stop pretending all 50 files matter equally. Alphabetical order is a hostile way to review machine-authored code — it buries the terrifying stuff between <code>AppServiceProvider.php</code> and <code>bootstrap.php</code>. So the list is ranked by how much attention a change actually deserves:</p><pre><code class="language-text">  ✓ M  database/migrations/2026_07_add_locale.php   migration      +31 −0
    M  .env.example                                 environment     +2 −0
    M  config/services.php                          config          +8 −1
    M  app/Http/Middleware/EnsureTeam.php           auth           +14 −3
    M  app/Models/User.php                          source          +6 −2
    A  app/Support/LocaleResolver.php               source         +48 −0
    M  tests/Feature/LocaleTest.php                 test           +22 −4
    M  composer.lock                                lockfile      +412 −87
</code></pre><p>Migrations, <code>.env</code>, <code>config/</code>, <code>routes/</code>, anything under <code>Middleware/</code> or <code>Policies/</code>, CI workflows and dependency manifests float to the top. Lockfiles, tests and docs sink. That <code>composer.lock</code> with its 412 added lines is technically the biggest change in the set and also the one you should spend the least time on, so it sits politely at the bottom where it belongs.</p><p>Then you walk the queue. <strong>Reviewed</strong> ticks the current file and jumps to the next one that needs eyes, and a counter tells you how far in you are (12/50 reviewed) — because the thing that kills a big review is losing your place and starting over.</p><p>Each file gets three buttons that map onto the three things you actually want to do:</p><ul><li><p><strong>Open</strong> — jumps to the first changed line in the real editor, with your language server, your definitions, your everything.</p></li><li><p><strong>Ask why</strong> — sends that specific file back to the agent: "You changed this. Explain what you did and why, and flag anything risky." The thing that wrote the code is right there. Ask it.</p></li><li><p><strong>Revert</strong> — undoes that one file. If the session created it, it's deleted. If it existed before, it comes back from <code>HEAD</code>. One file, not the whole session.</p></li></ul><p>There's a small design decision in here we're quietly pleased with. The session boundary is a git checkpoint taken when the agent starts — so "the changeset" means precisely what this session did, not everything I've been fiddling with since Tuesday.</p><p>But if there's no checkpoint, it doesn't sulk. It reviews everything uncommitted instead. Which means the whole thing works fine when you ran Claude Code in a separate terminal window, outside the editor entirely. We didn't plan that fallback as a feature. It just turned out to be the most common case.</p><h2>0.9.8: letting the diff read you back</h2><p>0.9.7 made reviewing pleasant. But something still nagged.</p><p>You can read 50 files carefully and still miss a <code>dd()</code>. Not because you're careless — because by file 34 your eyes are doing pattern-matching, not reading. And agents have tells: specific, recurring, boringly predictable things they leave behind when a session goes long.</p><p>So in 0.9.8, e reads the diff too.</p><p>Not a full static-analysis pass — you have tools for that, and they'd drown you in findings about code you didn't touch. These checks look only at what the session actually added or removed. Narrow scope, high signal:</p><pre><code class="language-php">// app/Services/Billing.php:88
$response = Http::post($url, $payload);
dd($response-&gt;json());                  ⚠ debug-leftover
</code></pre><pre><code class="language-php">// app/Services/Api.php:12
$apiKey = 'sk-live-4eC39HqLyjWDarjtT1zdp7dc';   ⛔ secret
</code></pre><pre><code class="language-php">// app/Repositories/OrderRepo.php:57
DB::raw("select * from orders where user_id = $userId")   ⛔ sql-injection
</code></pre><pre><code class="language-php">// database/migrations/2026_07_cleanup.php:19
$table-&gt;dropColumn('legacy_email');     ⛔ destructive-migration
</code></pre><p>And the one that genuinely made us sit up, because it's about something that disappeared:</p><pre><code class="language-diff">  // app/Http/Controllers/PostController.php
-        $this-&gt;authorize('update', $post);        ⛔ auth-removed
         return $post-&gt;update($request-&gt;all());
</code></pre><p>An authorization check that quietly went away during a refactor is the kind of thing that reads as cleaner code right up until it reads as a CVE. A diff-scoped check catches it because deletion is exactly what it's looking for.</p><p>The full set covers debug leftovers, hardcoded secrets, interpolated raw SQL, destructive migration steps, <code>.env</code> value changes, deleted authorization checks, <code>eval</code>/<code>shell_exec</code>/<code>unsafe {</code>, skipped or focused tests (<code>it.only(</code>, <code>#[ignore]</code>), weakened safety checks (<code>rejectUnauthorized: false</code>, <code>chmod 777</code>), removed tests, large one-sided deletions, plus gentler notes for TODOs and blocking <code>sleep()</code>s.</p><p>Each finding shows up above the diff with an <strong>Ask</strong> button, so you can hand it straight back: "a review check flagged this — is it intentional?"</p><h3>A small confession about <code>dd(</code></h3><p>Here's a detail we like, because it's the difference between a demo and a tool you'd actually leave switched on.</p><p>The first version of the debug check looked for <code>dd(</code> in added lines. Simple, obvious, and immediately wrong — because <code>add(</code> contains <code>dd(</code>. So does <code>Model::add(</code>. So does every <code>paddington(</code> you'll ever write. Meanwhile <code>ray(</code> cheerfully matched <code>array(</code>, which in a PHP codebase is roughly everywhere.</p><p>A linter that cries wolf on <code>array(</code> is a linter you turn off in an hour, and then it's worth nothing.</p><p>So the matching became call-aware: a match only counts if the character before it isn't part of an identifier. <code>$this-&gt;dd($x)</code> flags. <code>pub fn add(a: i32)</code> doesn't. <code>array(1, 2)</code> doesn't. <code>usleep(100)</code> reports once as <code>usleep</code>, not twice.</p><p>It's a handful of lines and a regression test. But it's the entire difference between a check you trust and a check you mute.</p><h3>The gate before the door</h3><p>Once you've read it all, one question remains — is this actually ready? — and the answer usually lives scattered across your short-term memory.</p><p>So there's a bar along the bottom that just... says it:</p><pre><code class="language-text">  Ready             8 files reviewed · tests green · no flags        [Run tests] [Commit &amp; PR]

  Notes             Tests have not been run · 6 of 8 files reviewed  [Run tests] [Commit &amp; PR]

  Needs attention   2 danger flags · Tests are failing               [Run tests] [Commit &amp; PR]
</code></pre><p><strong>Run tests</strong> fills in the missing piece with your project's real suite.</p><p>Note that <strong>Needs attention</strong> doesn't disable the button. It's advice, not a gate you have to argue with — sometimes that danger flag is a deliberate, considered choice, and a tool that won't let you say "yes, I know" is a tool that gets worked around. It just makes sure you can't ship a red suite by accident.</p><h3>And then, the PR — from here</h3><p>Which brings us back to GitHub, and to what it's actually for.</p><p><strong>Commit &amp; PR</strong> takes the reviewed changeset and, without you leaving the editor:</p><ol><li><p>creates a branch derived from what changed (<code>agent/app-models</code>) — deterministic, so re-running gives you the same name,</p></li><li><p>commits in logical groups, in dependency order, each with a Conventional Commits subject,</p></li><li><p>pushes and sets the upstream,</p></li><li><p>opens the PR via <code>gh</code>.</p></li></ol><p>That second step is the one worth pausing on. One giant "agent changes" commit is a bad artifact — unbisectable, unrevertable, unreadable in six months. So the changeset is split by what things are, and ordered so foundations land before the code standing on them:</p><pre><code class="language-text">  chore(deps): update composer.json      composer.json, composer.lock
  feat(db): update database/migrations   2026_07_add_locale.php
  chore(config): update config           config/services.php, .env.example
  feat(auth): update EnsureTeam          app/Http/Middleware/EnsureTeam.php
  feat: update app                       app/Models/User.php, app/Support/LocaleResolver.php
  test: update LocaleTest                tests/Feature/LocaleTest.php
</code></pre><p>Dependencies, then schema, then config, then auth, then code, then tests. It reads like a story instead of a landslide.</p><p>And the PR description writes itself from what just happened:</p><pre><code class="language-markdown">## Summary

Added per-user locale resolution, with a migration for the new column and
a middleware that resolves the active team's default.

## Changes

### feat(db): update database/migrations
- `database/migrations/2026_07_add_locale.php`

### feat(auth): update EnsureTeam
- `app/Http/Middleware/EnsureTeam.php`

## Review

- 8/8 files reviewed
- Tests: passing
- Flags: 0 danger, 1 warning(s)
- Verdict: ready
</code></pre><p>That last section is the bit that changes the conversation. Your reviewer isn't opening a wall of machine-written code cold — they're opening code that says a human walked every file, the suite is green, and here's what the checks found. That's a genuinely different ask.</p><p>The summary itself can come from the agent. <strong>Summarize</strong> asks it to describe its own work and flag anything risky, and it can hand that write-up back through the editor's sync socket (<code>review_summary</code>) so it lands in the PR body. The thing that did the work explains the work. You keep the veto.</p><h2>How it's put together</h2><p>A note for the curious, because the shape of this matters more than it sounds.</p><p>Nearly all of it is a pure, dependency-free crate (<code>e-review</code>) that turns unified-diff text into a structured, risk-ranked changeset — parsing, ranking, flagging, commit planning, PR text. No git, no UI, no I/O. 33 unit tests that run in about a millisecond.</p><p>Which meant we could get the thinking right — is <code>.env.example</code> an environment file or an example? does a rename keep its review tick when the diff is re-read? does <code>add(</code> trigger <code>dd(</code>? — before drawing a single pixel. The messy parts are deliberately small and pushed to the edges: <code>e_core::git</code> runs the commands, the panel draws the boxes.</p><p>There's one plumbing detail worth mentioning, because it caused an annoying bug in an early draft. <code>git diff &lt;checkpoint&gt;</code> only shows tracked files, so brand-new files an agent created were invisible in the review — which is precisely backwards, since new files are the ones you most want to look at. The obvious fix is <code>git add -N</code>, but we didn't love reaching into your index while you were mid-review. So untracked files get their diffs synthesized as new-file hunks instead, capped so a stray 4 MB asset doesn't turn your review into a wall of base64.</p><p>Your index stays untouched. Nothing to clean up if you walk away.</p><h2>Where this leaves us</h2><p>e 0.9.7 and 0.9.8 are, in the end, one idea in two parts: the review should happen where the code is.</p><p>Not because GitHub is bad — the PR is still where your team talks, and where the record lives. But by the time the diff gets there, the interesting work should already be done: read, flagged, tested, and either kept or quietly reverted, in a place that could actually run it.</p><p>The agent writes. You decide. And the silence after the spinner stops has something to do now.</p><p>⌘⌥V. Available in e 0.9.8, alongside a wider agent panel, selectable terminal output, and clickable file paths in agent output.</p>