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 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 one tool: **`open_review`**
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
- startReview
4
- } from "./chunk-LOX6GE37.js";
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 Configuration
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
- ### 2. First-Run Onboarding
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
- ### 3. Open the Review
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
- ### 4. Handle the Result
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
- ### 5. Error Handling
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 toolName = "mcp__diffprism__open_review";
177
- if (allow.includes(toolName) && !force) {
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
- if (!allow.includes(toolName)) {
181
- allow.push(toolName);
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.12.2" : "0.0.0-dev");
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();