diffprism 0.20.2 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -12,11 +12,12 @@ DiffPrism gives you a visual review step for AI-written code — stage your chan
|
|
|
12
12
|
- **Review briefing bar** — summary stats, complexity scoring, test coverage gaps, pattern flags, and dependency tracking
|
|
13
13
|
- **Agent reasoning panel** — see why the AI made each change
|
|
14
14
|
- **Dark/light mode** — toggle with theme persistence
|
|
15
|
-
- **Keyboard shortcuts** — `j`/`k` navigate files, `s` cycles file status
|
|
15
|
+
- **Keyboard shortcuts** — `j`/`k` navigate files, `s` cycles file status, `Cmd/Ctrl+Enter` saves comments, `?` toggles hotkey guide
|
|
16
16
|
- **Three-way decisions** — approve, request changes, or approve with comments
|
|
17
17
|
- **Branch display** — current git branch shown in the review header
|
|
18
18
|
- **Global server mode** — `diffprism server` runs a persistent multi-session review server, multiple agents post reviews to one browser tab
|
|
19
|
-
- **Multi-session UI** — session list with status badges, branch info, file counts, and change stats
|
|
19
|
+
- **Multi-session UI** — session list with live status badges, branch info, file counts, and change stats; stale sessions auto-expire
|
|
20
|
+
- **One-command setup & teardown** — `diffprism setup` configures Claude Code integration, `diffprism teardown` cleanly reverses it
|
|
20
21
|
|
|
21
22
|
## Quick Start
|
|
22
23
|
|
|
@@ -37,6 +38,14 @@ This single command configures everything:
|
|
|
37
38
|
|
|
38
39
|
After running, restart Claude Code. The first time you use `/review`, Claude will ask your preferences and save them to `diffprism.config.json`.
|
|
39
40
|
|
|
41
|
+
To remove DiffPrism from a project:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx diffprism teardown
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This reverses all changes made by `setup`: removes DiffPrism entries from `.mcp.json`, permissions, hooks, the skill file, `.gitignore`, and the `.diffprism/` directory. Non-DiffPrism entries are preserved.
|
|
48
|
+
|
|
40
49
|
See the [full setup guide](docs/claude-setup.md) for manual configuration, Claude Desktop config, troubleshooting, and advanced options.
|
|
41
50
|
|
|
42
51
|
### Use from the CLI
|
|
@@ -64,6 +73,18 @@ diffprism review --staged --title "Add auth middleware"
|
|
|
64
73
|
|
|
65
74
|
A browser window opens with the diff viewer. Review the changes and click **Approve**, **Request Changes**, or **Approve with Comments**.
|
|
66
75
|
|
|
76
|
+
### Quick Start (setup + watch in one command)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Configure Claude Code integration and start watching in one step
|
|
80
|
+
diffprism start
|
|
81
|
+
|
|
82
|
+
# With flags
|
|
83
|
+
diffprism start --staged --title "Working on auth"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This runs `diffprism setup` (silently, if already configured) then starts watch mode. Ideal for first-time use or when switching projects.
|
|
87
|
+
|
|
67
88
|
### Watch Mode (live-updating)
|
|
68
89
|
|
|
69
90
|
Keep a persistent browser tab that auto-refreshes as files change — ideal for reviewing while an agent is working:
|
|
@@ -102,13 +123,16 @@ diffprism server status
|
|
|
102
123
|
diffprism server stop
|
|
103
124
|
```
|
|
104
125
|
|
|
105
|
-
When the global server is running, MCP tools automatically detect it and route reviews there instead of opening ephemeral browser tabs. Each review appears as a session in the multi-session UI — click to switch between them.
|
|
126
|
+
When the global server is running, MCP tools automatically detect it and route reviews there instead of opening ephemeral browser tabs. Each review appears as a session in the multi-session UI — click to switch between them. Session status badges update live, and submitted sessions auto-expire after 5 minutes.
|
|
106
127
|
|
|
107
128
|
**Global setup** (optional, `diffprism server` runs this automatically):
|
|
108
129
|
|
|
109
130
|
```bash
|
|
110
131
|
# Configure skill + permissions globally (no git repo required)
|
|
111
132
|
diffprism setup --global
|
|
133
|
+
|
|
134
|
+
# Remove global configuration
|
|
135
|
+
diffprism teardown --global
|
|
112
136
|
```
|
|
113
137
|
|
|
114
138
|
Per-project MCP registration (`.mcp.json`) is still needed via `diffprism setup` in each project.
|
|
@@ -123,14 +147,14 @@ Opens a browser-based code review. Blocks until the engineer submits their decis
|
|
|
123
147
|
|
|
124
148
|
| Parameter | Required | Description |
|
|
125
149
|
|---------------|----------|-------------------------------------------------------------------|
|
|
126
|
-
| `diff_ref` | Yes | `"staged"`, `"unstaged"`, or a git ref range (e.g. `"HEAD~3..HEAD"`, `"main..feature"`) |
|
|
150
|
+
| `diff_ref` | Yes | `"staged"`, `"unstaged"`, `"working-copy"` (staged+unstaged grouped), or a git ref range (e.g. `"HEAD~3..HEAD"`, `"main..feature"`) |
|
|
127
151
|
| `title` | No | Title displayed in the review UI |
|
|
128
152
|
| `description` | No | Description of the changes |
|
|
129
153
|
| `reasoning` | No | Agent reasoning about why the changes were made (shown in the reasoning panel) |
|
|
130
154
|
|
|
131
155
|
### `update_review_context`
|
|
132
156
|
|
|
133
|
-
Pushes reasoning/context to a running
|
|
157
|
+
Pushes reasoning/context to a running DiffPrism session (watch mode or global server). Non-blocking — returns immediately.
|
|
134
158
|
|
|
135
159
|
| Parameter | Required | Description |
|
|
136
160
|
|---------------|----------|------------------------------------------------|
|
|
@@ -140,9 +164,12 @@ Pushes reasoning/context to a running `diffprism watch` session. Non-blocking
|
|
|
140
164
|
|
|
141
165
|
### `get_review_result`
|
|
142
166
|
|
|
143
|
-
Fetches the most recent review result from a
|
|
167
|
+
Fetches the most recent review result from a DiffPrism session (watch mode or global server). The result is marked as consumed after retrieval so it won't be returned again.
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
| Parameter | Required | Description |
|
|
170
|
+
|-----------|----------|-------------------------------------------------------------------|
|
|
171
|
+
| `wait` | No | If `true`, poll until a result is available (blocks up to timeout) |
|
|
172
|
+
| `timeout` | No | Max wait time in seconds when `wait=true` (default: 300, max: 600) |
|
|
146
173
|
|
|
147
174
|
**Returns (all tools):** A `ReviewResult` JSON object:
|
|
148
175
|
|
|
@@ -195,7 +222,7 @@ packages/git — Git diff extraction + unified diff parser
|
|
|
195
222
|
packages/analysis — Deterministic review briefing (complexity, test gaps, patterns)
|
|
196
223
|
packages/ui — React 19 + Vite 6 + Tailwind + Zustand diff viewer + session list
|
|
197
224
|
packages/mcp-server — MCP tool server, auto-routes to global server when available
|
|
198
|
-
cli/ — Commander CLI (review,
|
|
225
|
+
cli/ — Commander CLI (review, start, watch, setup, teardown, server commands)
|
|
199
226
|
```
|
|
200
227
|
|
|
201
228
|
### Requirements
|
|
@@ -207,4 +234,5 @@ cli/ — Commander CLI (review, serve, setup, server commands)
|
|
|
207
234
|
## Documentation
|
|
208
235
|
|
|
209
236
|
- [Claude Code / Claude Desktop Setup Guide](docs/claude-setup.md) — detailed MCP configuration, auto-approval, and troubleshooting
|
|
237
|
+
- [Workflows Guide](docs/workflows.md) — ephemeral, watch, and global server modes explained
|
|
210
238
|
- [UX Design Notes](docs/ux-design-notes.md) — design decisions, CLI defaults rationale, and multi-agent workflow thinking
|
package/dist/bin.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
startGlobalServer,
|
|
7
7
|
startReview,
|
|
8
8
|
startWatch
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3IVV6O45.js";
|
|
10
10
|
|
|
11
11
|
// cli/src/index.ts
|
|
12
12
|
import { Command } from "commander";
|
|
@@ -832,7 +832,7 @@ async function serverStop() {
|
|
|
832
832
|
|
|
833
833
|
// cli/src/index.ts
|
|
834
834
|
var program = new Command();
|
|
835
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
835
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.21.1" : "0.0.0-dev");
|
|
836
836
|
program.command("review [ref]").description("Open a browser-based diff review").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").option("--dev", "Use Vite dev server with HMR instead of static files").action(review);
|
|
837
837
|
program.command("start [ref]").description("Set up DiffPrism and start watching for changes").option("--staged", "Watch staged changes").option("--unstaged", "Watch unstaged changes").option("-t, --title <title>", "Review title").option("--interval <ms>", "Poll interval in milliseconds (default: 1000)").option("--dev", "Use Vite dev server with HMR instead of static files").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action(start);
|
|
838
838
|
program.command("watch [ref]").description("Start a persistent diff watcher with live-updating browser UI").option("--staged", "Watch staged changes").option("--unstaged", "Watch unstaged changes").option("-t, --title <title>", "Review title").option("--interval <ms>", "Poll interval in milliseconds (default: 1000)").option("--dev", "Use Vite dev server with HMR instead of static files").action(watch);
|
|
@@ -1447,6 +1447,9 @@ import { randomUUID } from "crypto";
|
|
|
1447
1447
|
import getPort3 from "get-port";
|
|
1448
1448
|
import open3 from "open";
|
|
1449
1449
|
import { WebSocketServer as WebSocketServer3, WebSocket as WebSocket3 } from "ws";
|
|
1450
|
+
var SUBMITTED_TTL_MS = 5 * 60 * 1e3;
|
|
1451
|
+
var ABANDONED_TTL_MS = 60 * 60 * 1e3;
|
|
1452
|
+
var CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
1450
1453
|
var sessions2 = /* @__PURE__ */ new Map();
|
|
1451
1454
|
var clientSessions = /* @__PURE__ */ new Map();
|
|
1452
1455
|
var sessionWatchers = /* @__PURE__ */ new Map();
|
|
@@ -1523,6 +1526,23 @@ function sendToSessionClients(sessionId, msg) {
|
|
|
1523
1526
|
}
|
|
1524
1527
|
}
|
|
1525
1528
|
}
|
|
1529
|
+
function broadcastSessionUpdate(session) {
|
|
1530
|
+
broadcastToAll({
|
|
1531
|
+
type: "session:updated",
|
|
1532
|
+
payload: toSummary(session)
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
function broadcastSessionRemoved(sessionId) {
|
|
1536
|
+
for (const [client, sid] of clientSessions.entries()) {
|
|
1537
|
+
if (sid === sessionId) {
|
|
1538
|
+
clientSessions.delete(client);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
broadcastToAll({
|
|
1542
|
+
type: "session:removed",
|
|
1543
|
+
payload: { sessionId }
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1526
1546
|
function hasViewersForSession(sessionId) {
|
|
1527
1547
|
for (const [client, sid] of clientSessions.entries()) {
|
|
1528
1548
|
if (sid === sessionId && client.readyState === WebSocket3.OPEN) {
|
|
@@ -1614,7 +1634,7 @@ async function handleApiRequest(req, res) {
|
|
|
1614
1634
|
const method = req.method ?? "GET";
|
|
1615
1635
|
const url = (req.url ?? "/").split("?")[0];
|
|
1616
1636
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1617
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1637
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1618
1638
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1619
1639
|
if (method === "OPTIONS") {
|
|
1620
1640
|
res.writeHead(204);
|
|
@@ -1696,7 +1716,7 @@ async function handleApiRequest(req, res) {
|
|
|
1696
1716
|
const result = JSON.parse(body);
|
|
1697
1717
|
session.result = result;
|
|
1698
1718
|
session.status = "submitted";
|
|
1699
|
-
|
|
1719
|
+
broadcastSessionUpdate(session);
|
|
1700
1720
|
jsonResponse(res, 200, { ok: true });
|
|
1701
1721
|
} catch {
|
|
1702
1722
|
jsonResponse(res, 400, { error: "Invalid request body" });
|
|
@@ -1750,6 +1770,7 @@ async function handleApiRequest(req, res) {
|
|
|
1750
1770
|
if (deleteParams) {
|
|
1751
1771
|
stopSessionWatcher(deleteParams.id);
|
|
1752
1772
|
if (sessions2.delete(deleteParams.id)) {
|
|
1773
|
+
broadcastSessionRemoved(deleteParams.id);
|
|
1753
1774
|
jsonResponse(res, 200, { ok: true });
|
|
1754
1775
|
} else {
|
|
1755
1776
|
jsonResponse(res, 404, { error: "Session not found" });
|
|
@@ -1802,7 +1823,7 @@ async function startGlobalServer(options = {}) {
|
|
|
1802
1823
|
if (session) {
|
|
1803
1824
|
session.status = "in_review";
|
|
1804
1825
|
session.hasNewChanges = false;
|
|
1805
|
-
|
|
1826
|
+
broadcastSessionUpdate(session);
|
|
1806
1827
|
const msg = {
|
|
1807
1828
|
type: "review:init",
|
|
1808
1829
|
payload: session.payload
|
|
@@ -1822,7 +1843,7 @@ async function startGlobalServer(options = {}) {
|
|
|
1822
1843
|
clientSessions.set(ws, session.id);
|
|
1823
1844
|
session.status = "in_review";
|
|
1824
1845
|
session.hasNewChanges = false;
|
|
1825
|
-
|
|
1846
|
+
broadcastSessionUpdate(session);
|
|
1826
1847
|
ws.send(JSON.stringify({
|
|
1827
1848
|
type: "review:init",
|
|
1828
1849
|
payload: session.payload
|
|
@@ -1840,7 +1861,7 @@ async function startGlobalServer(options = {}) {
|
|
|
1840
1861
|
if (session) {
|
|
1841
1862
|
session.result = msg.payload;
|
|
1842
1863
|
session.status = "submitted";
|
|
1843
|
-
|
|
1864
|
+
broadcastSessionUpdate(session);
|
|
1844
1865
|
}
|
|
1845
1866
|
}
|
|
1846
1867
|
} else if (msg.type === "session:select") {
|
|
@@ -1850,7 +1871,7 @@ async function startGlobalServer(options = {}) {
|
|
|
1850
1871
|
session.status = "in_review";
|
|
1851
1872
|
session.hasNewChanges = false;
|
|
1852
1873
|
startSessionWatcher(session.id);
|
|
1853
|
-
|
|
1874
|
+
broadcastSessionUpdate(session);
|
|
1854
1875
|
ws.send(JSON.stringify({
|
|
1855
1876
|
type: "review:init",
|
|
1856
1877
|
payload: session.payload
|
|
@@ -1881,6 +1902,19 @@ async function startGlobalServer(options = {}) {
|
|
|
1881
1902
|
httpServer.on("error", reject);
|
|
1882
1903
|
httpServer.listen(httpPort, () => resolve());
|
|
1883
1904
|
});
|
|
1905
|
+
function cleanupExpiredSessions() {
|
|
1906
|
+
const now = Date.now();
|
|
1907
|
+
for (const [id, session] of sessions2.entries()) {
|
|
1908
|
+
const age = now - session.createdAt;
|
|
1909
|
+
const expired = session.status === "submitted" && age > SUBMITTED_TTL_MS || session.status === "pending" && age > ABANDONED_TTL_MS;
|
|
1910
|
+
if (expired) {
|
|
1911
|
+
stopSessionWatcher(id);
|
|
1912
|
+
sessions2.delete(id);
|
|
1913
|
+
broadcastSessionRemoved(id);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
const cleanupTimer = setInterval(cleanupExpiredSessions, CLEANUP_INTERVAL_MS);
|
|
1884
1918
|
const serverInfo = {
|
|
1885
1919
|
httpPort,
|
|
1886
1920
|
wsPort,
|
|
@@ -1907,6 +1941,7 @@ Waiting for reviews...
|
|
|
1907
1941
|
}
|
|
1908
1942
|
};
|
|
1909
1943
|
async function stop() {
|
|
1944
|
+
clearInterval(cleanupTimer);
|
|
1910
1945
|
stopAllWatchers();
|
|
1911
1946
|
if (wss) {
|
|
1912
1947
|
for (const client of wss.clients) {
|
package/dist/mcp-server.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
readReviewResult,
|
|
8
8
|
readWatchFile,
|
|
9
9
|
startReview
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3IVV6O45.js";
|
|
11
11
|
|
|
12
12
|
// packages/mcp-server/src/index.ts
|
|
13
13
|
import fs from "fs";
|
|
@@ -76,7 +76,7 @@ async function reviewViaGlobalServer(serverInfo, diffRef, options) {
|
|
|
76
76
|
async function startMcpServer() {
|
|
77
77
|
const server = new McpServer({
|
|
78
78
|
name: "diffprism",
|
|
79
|
-
version: true ? "0.
|
|
79
|
+
version: true ? "0.21.1" : "0.0.0-dev"
|
|
80
80
|
});
|
|
81
81
|
server.tool(
|
|
82
82
|
"open_review",
|