From 1e882f086966b1b90046fb74ba5fdff62567185b Mon Sep 17 00:00:00 2001 From: "Fredrik Liljegren (Claude Code Claude Opus 5)" Date: Tue, 8 Sep 2026 16:44:51 +0200 Subject: [PATCH] feat: the agent names the findings behind an alert, and alerted pull requests sit at the top of the inbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The preparing agent can now print an ALERT-FINDINGS line beside its ALERT, naming the inline findings that make the pull request one to look at now — by the thread ids `diffity agent comment` printed, as 8-character prefixes or full uuids, spaced or comma-separated. They are kept on the row in a new `alert_findings` column, and a pull request with an alert is listed under a new Alerted section above Ready, on the page and in `diffity inbox status`, most recently prepared first. A posted review still takes the row to Handled, and an alert notifies from either list. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Bwp5QefjsjMFeq6CK6cT6w --- README.md | 2 +- package-lock.json | 12 +-- packages/api/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/commands/inbox.ts | 21 +++-- packages/cli/src/inbox/page.ts | 26 +++++-- packages/cli/src/inbox/prepare.ts | 4 +- packages/cli/src/inbox/prompt.ts | 44 ++++++++--- packages/cli/src/inbox/store.ts | 32 +++++++- packages/cli/src/inbox/tick.ts | 2 + packages/cli/src/inbox/view.ts | 17 ++-- packages/cli/tests/inbox-daemon.test.ts | 2 +- packages/cli/tests/inbox-handled.test.ts | 91 +++++++++++++++++++--- packages/cli/tests/inbox-open.test.ts | 2 +- packages/cli/tests/inbox-page.test.ts | 16 ++++ packages/cli/tests/inbox-prepare.test.ts | 8 +- packages/cli/tests/inbox-reconcile.test.ts | 2 +- packages/cli/tests/inbox-store.test.ts | 40 +++++++++- packages/cli/tests/inbox-tick.test.ts | 44 ++++++++--- packages/cli/tests/inbox-units.test.ts | 36 ++++++++- packages/git/package.json | 2 +- packages/github/package.json | 2 +- packages/parser/package.json | 2 +- packages/ui/package.json | 2 +- 24 files changed, 335 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 3ff0271..19e1701 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ On first run it writes `~/.diffity/inbox/config.json`: | `worktreesDir` | Where each pull request gets its worktree. | | `skipTitles` | Regular expressions matched against the title, one per line in the page — `\(payments\)`, `Release$`. A match skips the pull request before any agent runs, and ↑ overrides it; the match is re-decided at every poll, so a retitle brings the pull request back to the queue. For what a regex cannot express, `filter`. Editable from the page's Settings panel. | | `filter` | Your own words on what does and doesn't need your attention, handed to the agent — it answers with a skip instead of reviewing when a PR matches (e.g. "Skip payments-focused PRs"). The agent has to load the skill and read the diff to decide, so every skip costs an agent run; `skipTitles` above costs nothing. Editable from the page's Settings panel. | -| `alertWhen` | Your own words on what needs you *now*. The agent judges each prepared review against them and flags the ones that match; the page notifies for those only — empty means every prepared review. Editable from the page's Settings panel. | +| `alertWhen` | Your own words on what needs you *now*. The agent judges each prepared review against them and flags the ones that match, naming the findings behind the flag; the flagged ones are listed under **Alerted**, above Ready, and the page notifies for those only — empty means every prepared review. Editable from the page's Settings panel. | | `alertPaths` | Globs against the pull request's changed paths, one per line in the page — `packages/shared/src/model/**`, `**/dbref/**`. A changed file matching one marks the review as needing you now, whatever the agent made of `alertWhen`. Editable from the page's Settings panel. | | `agent.model` | `--model` for the review agent; `null` leaves its own default. Editable from the page. | | `agent.effort` | `--effort`: `low`, `medium`, `high`, `xhigh` or `max`; `null` leaves its own default. Editable from the page. | diff --git a/package-lock.json b/package-lock.json index cf63643..100a4a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8457,7 +8457,7 @@ }, "packages/api": { "name": "@diffity/api", - "version": "0.10.29", + "version": "0.10.30", "dependencies": { "@diffity/parser": "*" }, @@ -8468,7 +8468,7 @@ }, "packages/cli": { "name": "@naturalcycles/diffity", - "version": "0.10.29", + "version": "0.10.30", "license": "MIT", "dependencies": { "commander": "^14.0.3", @@ -8492,7 +8492,7 @@ }, "packages/git": { "name": "@diffity/git", - "version": "0.10.29", + "version": "0.10.30", "devDependencies": { "@types/node": "^25.5.0", "typescript": "^5.9.3", @@ -8501,7 +8501,7 @@ }, "packages/github": { "name": "@diffity/github", - "version": "0.10.29", + "version": "0.10.30", "dependencies": { "@diffity/api": "*", "@diffity/parser": "*" @@ -8514,7 +8514,7 @@ }, "packages/parser": { "name": "@diffity/parser", - "version": "0.10.29", + "version": "0.10.30", "devDependencies": { "typescript": "^5.9.3", "vitest": "^4.1.0" @@ -8522,7 +8522,7 @@ }, "packages/ui": { "name": "@diffity/ui", - "version": "0.10.29", + "version": "0.10.30", "dependencies": { "@diffity/api": "*", "@diffity/parser": "*", diff --git a/packages/api/package.json b/packages/api/package.json index 639dfbd..f338e50 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@diffity/api", - "version": "0.10.29", + "version": "0.10.30", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index 8fa4dc4..f0834a4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@naturalcycles/diffity", - "version": "0.10.29", + "version": "0.10.30", "description": "Agent-agnostic, GitHub-style diff viewer and code review tool with a live agent loop", "type": "module", "bin": { diff --git a/packages/cli/src/commands/inbox.ts b/packages/cli/src/commands/inbox.ts index f099e4e..a5c109b 100644 --- a/packages/cli/src/commands/inbox.ts +++ b/packages/cli/src/commands/inbox.ts @@ -6,7 +6,7 @@ import { inboxConfigPath, inboxStorePath } from '../inbox/paths.js'; import { InboxStore, type RunTotals } from '../inbox/store.js'; import { runDaemon } from '../inbox/daemon.js'; import { allowFromEnv, mcpGateDecision } from '../inbox/mcp-gate.js'; -import { buildView } from '../inbox/view.js'; +import { buildView, type InboxRow } from '../inbox/view.js'; import { localHhMm, localWhen, minutesOf, money, tokensLabel } from '../inbox/runs.js'; export function registerInboxCommand(program: Command): void { @@ -87,16 +87,15 @@ export function registerInboxCommand(program: Command): void { return; } - if (view.ready.length === 0 && view.working.length === 0 && view.handled.length === 0 - && view.other.length === 0 && view.dismissed.length === 0) { + if (view.alerted.length === 0 && view.ready.length === 0 && view.working.length === 0 + && view.handled.length === 0 && view.other.length === 0 && view.dismissed.length === 0) { console.log(pc.dim('Nothing in the inbox yet. Run `diffity inbox` to start watching.')); spent(view); return; } - section('Ready to review', view.ready.map(row => - ` ${sizeBadge(row)} ${pc.bold(`${row.repo}#${row.number}`)} ${row.title}${row.summary ? pc.dim(` ${row.summary}`) : ''}${row.alert ? pc.red(` ⚠ ${row.alert}`) : ''}${row.stale ? pc.yellow(' (stale — new commits)') : ''}`, - )); + section('Alerted', view.alerted.map(preparedLine)); + section('Ready to review', view.ready.map(preparedLine)); section('Queue', view.working.map(row => ` ${pc.dim(row.status.padEnd(9))} ${row.repo}#${row.number} ${row.title} ${pc.dim(row.statusReason ?? '')}`, )); @@ -199,6 +198,16 @@ function section(title: string, lines: string[]): void { } } +/** A prepared review as one line: its size, what was found, and what was raised about it. */ +function preparedLine(row: InboxRow): string { + const findings = row.alertFindings.length; + return ` ${sizeBadge(row)} ${pc.bold(`${row.repo}#${row.number}`)} ${row.title}` + + `${row.summary ? pc.dim(` ${row.summary}`) : ''}` + + `${row.alert ? pc.red(` ⚠ ${row.alert}`) : ''}` + + `${findings ? pc.red(` ${findings} finding${findings === 1 ? '' : 's'}`) : ''}` + + `${row.stale ? pc.yellow(' (stale — new commits)') : ''}`; +} + function sizeBadge(row: { additions: number; deletions: number }): string { return pc.dim(`+${row.additions}/-${row.deletions}`.padEnd(12)); } diff --git a/packages/cli/src/inbox/page.ts b/packages/cli/src/inbox/page.ts index c7d880c..a13a856 100644 --- a/packages/cli/src/inbox/page.ts +++ b/packages/cli/src/inbox/page.ts @@ -113,6 +113,10 @@ export function inboxPage(): string {
+