diffprism 0.12.2 → 0.13.0
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 +38 -1
- package/dist/bin.js +140 -12
- package/dist/{chunk-LOX6GE37.js → chunk-TYUDIWD6.js} +423 -12
- package/dist/mcp-server.js +103 -2
- package/package.json +1 -1
- package/ui-dist/assets/{index-CpWq5K0r.js → index-Cs1-Woa_.js} +24 -24
- package/ui-dist/assets/index-fRamHsDg.css +1 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-GiCSAMgl.css +0 -1
package/README.md
CHANGED
|
@@ -60,9 +60,36 @@ diffprism review --staged --title "Add auth middleware"
|
|
|
60
60
|
|
|
61
61
|
A browser window opens with the diff viewer. Review the changes and click **Approve**, **Request Changes**, or **Approve with Comments**.
|
|
62
62
|
|
|
63
|
+
### Watch Mode (live-updating)
|
|
64
|
+
|
|
65
|
+
Keep a persistent browser tab that auto-refreshes as files change — ideal for reviewing while an agent is working:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Watch staged changes, auto-refresh on every change
|
|
69
|
+
diffprism watch --staged
|
|
70
|
+
|
|
71
|
+
# Watch all changes with custom poll interval
|
|
72
|
+
diffprism watch --interval 2000
|
|
73
|
+
|
|
74
|
+
# Watch unstaged changes
|
|
75
|
+
diffprism watch --unstaged
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
When `diffprism watch` is running:
|
|
79
|
+
- The browser tab stays open and updates diffs + analysis within 1-2s of file changes
|
|
80
|
+
- Submit a review and it stays open, waiting for the next change
|
|
81
|
+
- File review statuses are preserved for unchanged files
|
|
82
|
+
- Claude Code's `/review` skill automatically detects the watch session and pushes reasoning without blocking
|
|
83
|
+
|
|
84
|
+
Stop the watcher with `Ctrl+C`.
|
|
85
|
+
|
|
63
86
|
## MCP Tool Reference
|
|
64
87
|
|
|
65
|
-
The MCP server exposes
|
|
88
|
+
The MCP server exposes two tools:
|
|
89
|
+
|
|
90
|
+
### `open_review`
|
|
91
|
+
|
|
92
|
+
Opens a browser-based code review. Blocks until the engineer submits their decision.
|
|
66
93
|
|
|
67
94
|
| Parameter | Required | Description |
|
|
68
95
|
|---------------|----------|-------------------------------------------------------------------|
|
|
@@ -71,6 +98,16 @@ The MCP server exposes one tool: **`open_review`**
|
|
|
71
98
|
| `description` | No | Description of the changes |
|
|
72
99
|
| `reasoning` | No | Agent reasoning about why the changes were made (shown in the reasoning panel) |
|
|
73
100
|
|
|
101
|
+
### `update_review_context`
|
|
102
|
+
|
|
103
|
+
Pushes reasoning/context to a running `diffprism watch` session. Non-blocking — returns immediately.
|
|
104
|
+
|
|
105
|
+
| Parameter | Required | Description |
|
|
106
|
+
|---------------|----------|------------------------------------------------|
|
|
107
|
+
| `reasoning` | No | Agent reasoning about the current changes |
|
|
108
|
+
| `title` | No | Updated title for the review |
|
|
109
|
+
| `description` | No | Updated description of the changes |
|
|
110
|
+
|
|
74
111
|
**Returns:** A `ReviewResult` JSON object:
|
|
75
112
|
|
|
76
113
|
```json
|
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
readWatchFile,
|
|
4
|
+
startReview,
|
|
5
|
+
startWatch
|
|
6
|
+
} from "./chunk-TYUDIWD6.js";
|
|
5
7
|
|
|
6
8
|
// cli/src/index.ts
|
|
7
9
|
import { Command } from "commander";
|
|
@@ -57,7 +59,26 @@ When the user invokes \`/review\`, open the current code changes in DiffPrism fo
|
|
|
57
59
|
|
|
58
60
|
## Steps
|
|
59
61
|
|
|
60
|
-
### 1. Check for
|
|
62
|
+
### 1. Check for Watch Mode
|
|
63
|
+
|
|
64
|
+
Before opening a new review, check if \`diffprism watch\` is already running. Look for \`.diffprism/watch.json\` at the git root. If it exists and the process is alive:
|
|
65
|
+
|
|
66
|
+
- **Do NOT call \`open_review\`** (the browser is already open with live-updating diffs)
|
|
67
|
+
- Instead, call \`mcp__diffprism__update_review_context\` to push your reasoning to the existing watch session
|
|
68
|
+
- Tell the user: "DiffPrism watch is running \u2014 pushed reasoning to the live review."
|
|
69
|
+
- Skip the remaining steps
|
|
70
|
+
|
|
71
|
+
### 1b. Check for Pending Review Feedback
|
|
72
|
+
|
|
73
|
+
If watch mode is running, call \`mcp__diffprism__get_review_result\` to check for pending review feedback from the developer. If a result is returned:
|
|
74
|
+
|
|
75
|
+
- **\`approved\`** \u2014 Acknowledge approval and continue with your current task.
|
|
76
|
+
- **\`approved_with_comments\`** \u2014 Note the comments, address any actionable feedback.
|
|
77
|
+
- **\`changes_requested\`** \u2014 Read the comments carefully, make the requested changes, then push updated reasoning via \`mcp__diffprism__update_review_context\`.
|
|
78
|
+
|
|
79
|
+
If no pending result, continue normally.
|
|
80
|
+
|
|
81
|
+
### 2. Check for Configuration
|
|
61
82
|
|
|
62
83
|
Look for \`diffprism.config.json\` at the project root. If it exists, read it for preferences:
|
|
63
84
|
|
|
@@ -74,7 +95,7 @@ Look for \`diffprism.config.json\` at the project root. If it exists, read it fo
|
|
|
74
95
|
- \`defaultDiffScope\`: \`"all"\`
|
|
75
96
|
- \`includeReasoning\`: \`true\`
|
|
76
97
|
|
|
77
|
-
###
|
|
98
|
+
### 3. First-Run Onboarding
|
|
78
99
|
|
|
79
100
|
If \`diffprism.config.json\` does **not** exist, ask the user these questions before proceeding:
|
|
80
101
|
|
|
@@ -94,7 +115,7 @@ If \`diffprism.config.json\` does **not** exist, ask the user these questions be
|
|
|
94
115
|
|
|
95
116
|
After collecting answers, create \`diffprism.config.json\` at the project root with the user's choices. Then proceed to open the review.
|
|
96
117
|
|
|
97
|
-
###
|
|
118
|
+
### 4. Open the Review
|
|
98
119
|
|
|
99
120
|
Call \`mcp__diffprism__open_review\` with:
|
|
100
121
|
|
|
@@ -103,7 +124,7 @@ Call \`mcp__diffprism__open_review\` with:
|
|
|
103
124
|
- \`description\`: A brief description of what changed and why.
|
|
104
125
|
- \`reasoning\`: If \`includeReasoning\` is \`true\`, include your reasoning about the implementation decisions.
|
|
105
126
|
|
|
106
|
-
###
|
|
127
|
+
### 5. Handle the Result
|
|
107
128
|
|
|
108
129
|
The tool blocks until the user submits their review in the browser. When it returns:
|
|
109
130
|
|
|
@@ -111,7 +132,7 @@ The tool blocks until the user submits their review in the browser. When it retu
|
|
|
111
132
|
- **\`approved_with_comments\`** \u2014 Note the comments, address any actionable feedback.
|
|
112
133
|
- **\`changes_requested\`** \u2014 Read the comments carefully, make the requested changes, and offer to open another review.
|
|
113
134
|
|
|
114
|
-
###
|
|
135
|
+
### 6. Error Handling
|
|
115
136
|
|
|
116
137
|
If the \`mcp__diffprism__open_review\` tool is not available:
|
|
117
138
|
- Tell the user: "The DiffPrism MCP server isn't configured. Run \`npx diffprism setup\` to set it up, then restart Claude Code."
|
|
@@ -173,12 +194,19 @@ function setupClaudeSettings(gitRoot, force) {
|
|
|
173
194
|
const existing = readJsonFile(filePath);
|
|
174
195
|
const permissions = existing.permissions ?? {};
|
|
175
196
|
const allow = permissions.allow ?? [];
|
|
176
|
-
const
|
|
177
|
-
|
|
197
|
+
const toolNames = [
|
|
198
|
+
"mcp__diffprism__open_review",
|
|
199
|
+
"mcp__diffprism__update_review_context",
|
|
200
|
+
"mcp__diffprism__get_review_result"
|
|
201
|
+
];
|
|
202
|
+
const allPresent = toolNames.every((t) => allow.includes(t));
|
|
203
|
+
if (allPresent && !force) {
|
|
178
204
|
return { action: "skipped", filePath };
|
|
179
205
|
}
|
|
180
|
-
|
|
181
|
-
allow.
|
|
206
|
+
for (const toolName of toolNames) {
|
|
207
|
+
if (!allow.includes(toolName)) {
|
|
208
|
+
allow.push(toolName);
|
|
209
|
+
}
|
|
182
210
|
}
|
|
183
211
|
permissions.allow = allow;
|
|
184
212
|
const action = fs.existsSync(filePath) ? "updated" : "created";
|
|
@@ -207,6 +235,39 @@ function setupSkill(gitRoot, global, force) {
|
|
|
207
235
|
fs.writeFileSync(filePath, skillContent);
|
|
208
236
|
return { action, filePath };
|
|
209
237
|
}
|
|
238
|
+
function setupStopHook(gitRoot, force) {
|
|
239
|
+
const filePath = path.join(gitRoot, ".claude", "settings.json");
|
|
240
|
+
const existing = readJsonFile(filePath);
|
|
241
|
+
const hooks = existing.hooks ?? {};
|
|
242
|
+
const stopHooks = hooks.Stop;
|
|
243
|
+
const hookCommand = "npx diffprism notify-stop";
|
|
244
|
+
if (stopHooks && !force) {
|
|
245
|
+
const hasHook = stopHooks.some((entry) => {
|
|
246
|
+
const innerHooks = entry.hooks;
|
|
247
|
+
return innerHooks?.some((h) => h.command === hookCommand);
|
|
248
|
+
});
|
|
249
|
+
if (hasHook) {
|
|
250
|
+
return { action: "skipped", filePath };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const hookEntry = {
|
|
254
|
+
matcher: "",
|
|
255
|
+
hooks: [
|
|
256
|
+
{
|
|
257
|
+
type: "command",
|
|
258
|
+
command: hookCommand
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
if (stopHooks && !force) {
|
|
263
|
+
stopHooks.push(hookEntry);
|
|
264
|
+
} else {
|
|
265
|
+
hooks.Stop = [hookEntry];
|
|
266
|
+
}
|
|
267
|
+
const action = fs.existsSync(filePath) ? "updated" : "created";
|
|
268
|
+
writeJsonFile(filePath, { ...existing, hooks });
|
|
269
|
+
return { action, filePath: filePath + " (Stop hook)" };
|
|
270
|
+
}
|
|
210
271
|
async function setup(flags) {
|
|
211
272
|
const gitRoot = findGitRoot(process.cwd());
|
|
212
273
|
if (!gitRoot) {
|
|
@@ -224,6 +285,8 @@ async function setup(flags) {
|
|
|
224
285
|
result[mcp.action === "skipped" ? "skipped" : mcp.action === "created" ? "created" : "updated"].push(mcp.filePath);
|
|
225
286
|
const settings = setupClaudeSettings(gitRoot, force);
|
|
226
287
|
result[settings.action === "skipped" ? "skipped" : settings.action === "created" ? "created" : "updated"].push(settings.filePath);
|
|
288
|
+
const hook = setupStopHook(gitRoot, force);
|
|
289
|
+
result[hook.action === "skipped" ? "skipped" : hook.action === "created" ? "created" : "updated"].push(hook.filePath);
|
|
227
290
|
const skill = setupSkill(gitRoot, global, force);
|
|
228
291
|
result[skill.action === "skipped" ? "skipped" : skill.action === "created" ? "created" : "updated"].push(skill.filePath);
|
|
229
292
|
if (result.created.length > 0) {
|
|
@@ -247,15 +310,80 @@ async function setup(flags) {
|
|
|
247
310
|
console.log(
|
|
248
311
|
"\nYou can now use /review in Claude Code to open a DiffPrism review."
|
|
249
312
|
);
|
|
313
|
+
console.log(
|
|
314
|
+
"Or run `diffprism watch --staged` for live-updating reviews."
|
|
315
|
+
);
|
|
250
316
|
console.log(
|
|
251
317
|
"If Claude Code is running, restart it to pick up the new configuration."
|
|
252
318
|
);
|
|
253
319
|
}
|
|
254
320
|
|
|
321
|
+
// cli/src/commands/watch.ts
|
|
322
|
+
async function watch(ref, flags) {
|
|
323
|
+
let diffRef;
|
|
324
|
+
if (flags.staged) {
|
|
325
|
+
diffRef = "staged";
|
|
326
|
+
} else if (flags.unstaged) {
|
|
327
|
+
diffRef = "unstaged";
|
|
328
|
+
} else if (ref) {
|
|
329
|
+
diffRef = ref;
|
|
330
|
+
} else {
|
|
331
|
+
diffRef = "all";
|
|
332
|
+
}
|
|
333
|
+
const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
|
|
334
|
+
try {
|
|
335
|
+
const handle = await startWatch({
|
|
336
|
+
diffRef,
|
|
337
|
+
title: flags.title,
|
|
338
|
+
cwd: process.cwd(),
|
|
339
|
+
dev: flags.dev,
|
|
340
|
+
pollInterval
|
|
341
|
+
});
|
|
342
|
+
const shutdown = async () => {
|
|
343
|
+
console.log("\nStopping watch...");
|
|
344
|
+
await handle.stop();
|
|
345
|
+
process.exit(0);
|
|
346
|
+
};
|
|
347
|
+
process.on("SIGINT", shutdown);
|
|
348
|
+
process.on("SIGTERM", shutdown);
|
|
349
|
+
await new Promise(() => {
|
|
350
|
+
});
|
|
351
|
+
} catch (err) {
|
|
352
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
353
|
+
console.error(`Error: ${message}`);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// cli/src/commands/notify-stop.ts
|
|
359
|
+
async function notifyStop() {
|
|
360
|
+
try {
|
|
361
|
+
const watchInfo = readWatchFile();
|
|
362
|
+
if (!watchInfo) {
|
|
363
|
+
process.exit(0);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const controller = new AbortController();
|
|
367
|
+
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
368
|
+
try {
|
|
369
|
+
await fetch(`http://localhost:${watchInfo.wsPort}/api/refresh`, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
signal: controller.signal
|
|
372
|
+
});
|
|
373
|
+
} finally {
|
|
374
|
+
clearTimeout(timeout);
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
255
381
|
// cli/src/index.ts
|
|
256
382
|
var program = new Command();
|
|
257
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
383
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.13.0" : "0.0.0-dev");
|
|
258
384
|
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);
|
|
385
|
+
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);
|
|
386
|
+
program.command("notify-stop").description("Signal the watch server to refresh (used by Claude Code hooks)").action(notifyStop);
|
|
259
387
|
program.command("serve").description("Start the MCP server for Claude Code integration").action(serve);
|
|
260
388
|
program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action(setup);
|
|
261
389
|
program.parse();
|