diffprism 0.37.0 → 0.37.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
@@ -1,14 +1,18 @@
1
1
  # DiffPrism
2
2
 
3
- Browser-based code review for agent-generated changes. Review what your AI wrote before it ships.
3
+ Your AI writes code you need to review it before it ships. But terminal diffs
4
+ are hard to read, and there's no way to leave structured feedback that the agent
5
+ actually acts on.
4
6
 
5
- ## Try It
7
+ DiffPrism opens a code review UI in your browser with syntax-highlighted diffs,
8
+ inline commenting, and structured decisions (approve / request changes) that
9
+ Claude reads and responds to.
6
10
 
7
- ```bash
8
- npx diffprism demo
9
- ```
11
+ ## How It Works
10
12
 
11
- A browser window opens with a sample code review. Click around leave comments, approve, see how it works. No git repo needed.
13
+ 1. Type `/review` in Claude Codeyour browser opens with the diff
14
+ 2. Review the changes, leave inline comments, approve or request changes
15
+ 3. Claude reads your decision and acts on it (commits, opens a PR, or fixes what you flagged)
12
16
 
13
17
  ## Setup for Claude Code
14
18
 
@@ -16,18 +20,12 @@ A browser window opens with a sample code review. Click around — leave comment
16
20
  npx diffprism setup
17
21
  ```
18
22
 
19
- Walks you through configuration. After restarting Claude Code, type `/review` to review your agent's changes.
20
-
21
- **Two ways to trigger a review:**
22
-
23
- 1. **Type `/review`** — Claude opens your current changes in DiffPrism's browser UI, waits for your decision, and acts on it (e.g., commits if you approve).
24
-
25
- 2. **Ask Claude to review** — Say "review my changes" or "open a review" and Claude will call the tool.
26
-
27
- The review blocks Claude until you submit your decision in the browser. If you request changes, Claude reads your comments and fixes them. If you approve via the quick action menu, Claude commits or opens a PR automatically.
23
+ Configures everything and opens a demo review so you can see it in action. Restart Claude Code afterward to load the MCP server.
28
24
 
29
25
  ## Use from the CLI
30
26
 
27
+ No setup needed — just run:
28
+
31
29
  ```bash
32
30
  diffprism review # Review all changes (staged + unstaged)
33
31
  diffprism review --staged # Staged changes only
package/dist/bin.js CHANGED
@@ -258,67 +258,6 @@ function setupSkill(gitRoot, global, force) {
258
258
  fs.writeFileSync(filePath, skillContent);
259
259
  return { action, filePath };
260
260
  }
261
- function cleanDiffprismHooks(gitRoot) {
262
- const filePath = path.join(gitRoot, ".claude", "settings.json");
263
- const existing = readJsonFile(filePath);
264
- const hooks = existing.hooks;
265
- if (!hooks) return { removed: 0 };
266
- const stopHooks = hooks.Stop;
267
- if (!Array.isArray(stopHooks) || stopHooks.length === 0) {
268
- return { removed: 0 };
269
- }
270
- const filtered = stopHooks.filter((entry) => {
271
- const innerHooks = entry.hooks;
272
- if (!Array.isArray(innerHooks)) return true;
273
- return !innerHooks.some((h) => {
274
- const cmd = h.command;
275
- return typeof cmd === "string" && cmd.includes("diffprism") && cmd.includes("notify-stop");
276
- });
277
- });
278
- const removed = stopHooks.length - filtered.length;
279
- if (removed > 0) {
280
- if (filtered.length > 0) {
281
- hooks.Stop = filtered;
282
- } else {
283
- delete hooks.Stop;
284
- }
285
- writeJsonFile(filePath, { ...existing, hooks });
286
- }
287
- return { removed };
288
- }
289
- function setupStopHook(gitRoot, force) {
290
- const filePath = path.join(gitRoot, ".claude", "settings.json");
291
- const existing = readJsonFile(filePath);
292
- const hooks = existing.hooks ?? {};
293
- const stopHooks = hooks.Stop;
294
- const hookCommand = "npx diffprism@latest notify-stop";
295
- if (stopHooks && !force) {
296
- const hasHook = stopHooks.some((entry) => {
297
- const innerHooks = entry.hooks;
298
- return innerHooks?.some((h) => h.command === hookCommand);
299
- });
300
- if (hasHook) {
301
- return { action: "skipped", filePath };
302
- }
303
- }
304
- const hookEntry = {
305
- matcher: "",
306
- hooks: [
307
- {
308
- type: "command",
309
- command: hookCommand
310
- }
311
- ]
312
- };
313
- if (stopHooks && !force) {
314
- stopHooks.push(hookEntry);
315
- } else {
316
- hooks.Stop = [hookEntry];
317
- }
318
- const action = fs.existsSync(filePath) ? "updated" : "created";
319
- writeJsonFile(filePath, { ...existing, hooks });
320
- return { action, filePath: filePath + " (Stop hook)" };
321
- }
322
261
  async function promptUser(question) {
323
262
  const rl = readline2.createInterface({
324
263
  input: process.stdin,
@@ -357,26 +296,6 @@ async function setupGitignore(gitRoot) {
357
296
  fs.writeFileSync(filePath, GITIGNORE_ENTRIES.map((e) => e + "\n").join(""));
358
297
  return { action: "created", filePath };
359
298
  }
360
- async function promptChoice(question, options) {
361
- const rl = readline2.createInterface({
362
- input: process.stdin,
363
- output: process.stdout
364
- });
365
- for (let i = 0; i < options.length; i++) {
366
- console.log(` ${i + 1}. ${options[i]}`);
367
- }
368
- return new Promise((resolve) => {
369
- rl.question(question, (answer) => {
370
- rl.close();
371
- const num = parseInt(answer.trim(), 10);
372
- if (num >= 1 && num <= options.length) {
373
- resolve(num - 1);
374
- } else {
375
- resolve(0);
376
- }
377
- });
378
- });
379
- }
380
299
  async function setup(flags) {
381
300
  const force = flags.force ?? false;
382
301
  const global = flags.global ?? false;
@@ -389,50 +308,32 @@ async function setup(flags) {
389
308
  }
390
309
  async function setupInteractive(flags) {
391
310
  const dev = flags.dev;
311
+ const demo2 = flags.demo !== false;
392
312
  const gitRoot = findGitRoot(process.cwd());
393
313
  console.log("\n Welcome to DiffPrism");
394
314
  console.log(" Browser-based code review for agent-generated changes.\n");
395
- const modeChoice = await promptChoice("\nHow will you use DiffPrism? ", [
396
- "With Claude Code (recommended)",
397
- "From the CLI only"
398
- ]);
399
- if (modeChoice === 0) {
400
- if (!gitRoot) {
401
- console.log("\n Not in a git repository \u2014 configuring globally.\n");
402
- const outcome2 = await setupBatch({ global: true, quiet: true });
403
- console.log(" Setting up for Claude Code...");
404
- console.log(" \u2713 Installed /review skill");
405
- console.log(" \u2713 Added tool permissions");
406
- console.log("\n Restart Claude Code, then type /review to start a review.\n");
407
- await offerDemo(dev);
408
- return outcome2;
409
- }
410
- console.log("\n Setting up for Claude Code...");
411
- const outcome = await setupBatch({ quiet: true });
315
+ let outcome;
316
+ if (!gitRoot) {
317
+ outcome = await setupBatch({ global: true, quiet: true });
318
+ console.log(" \u2713 Configured for Claude Code (global).");
319
+ } else {
320
+ outcome = await setupBatch({ quiet: true });
412
321
  if (outcome.created.length > 0 || outcome.updated.length > 0) {
413
- console.log(" \u2713 Registered MCP server (.mcp.json)");
414
- console.log(" \u2713 Added tool permissions (.claude/settings.json)");
415
- console.log(" \u2713 Installed /review skill");
416
- console.log(" \u2713 Added .diffprism to .gitignore");
322
+ console.log(" \u2713 Configured for Claude Code.");
417
323
  } else {
418
- console.log(" \u2713 Already configured");
324
+ console.log(" \u2713 Already configured.");
419
325
  }
420
- console.log("\n Restart Claude Code, then type /review to start a review.\n");
421
- await offerDemo(dev);
422
- return outcome;
423
- }
424
- console.log("\n DiffPrism is ready to use.");
425
- console.log(" Run `diffprism review` in any git repo to review changes.\n");
426
- await offerDemo(dev);
427
- return { created: [], updated: [], skipped: [] };
428
- }
429
- async function offerDemo(dev) {
430
- const wantsDemo = await promptUser("Try a demo review now? (Y/n) ");
431
- if (wantsDemo) {
432
- console.log("");
433
- const { demo: demo2 } = await import("./demo-JH5YOKTZ.js");
434
- await demo2({ dev });
435
326
  }
327
+ console.log(" Restart Claude Code, then type /review to start a review.\n");
328
+ if (demo2) {
329
+ await runDemo(dev);
330
+ }
331
+ return outcome;
332
+ }
333
+ async function runDemo(dev) {
334
+ console.log("");
335
+ const { demo: demo2 } = await import("./demo-JH5YOKTZ.js");
336
+ await demo2({ dev });
436
337
  }
437
338
  async function setupBatch(flags) {
438
339
  const force = flags.force ?? false;
@@ -478,21 +379,11 @@ async function setupBatch(flags) {
478
379
  result[mcp.action].push(mcp.filePath);
479
380
  const settings = setupClaudeSettings(gitRoot, force);
480
381
  result[settings.action].push(settings.filePath);
481
- const cleaned = cleanDiffprismHooks(gitRoot);
482
- if (cleaned.removed > 0 && !quiet) {
483
- console.log(` Cleaned ${cleaned.removed} stale hook(s)`);
484
- }
485
- const hook = setupStopHook(gitRoot, force);
486
- result[hook.action].push(hook.filePath);
487
382
  const skill = setupSkill(gitRoot, false, force);
488
383
  result[skill.action].push(skill.filePath);
489
384
  if (!quiet) {
490
- printSummary(result, gitRoot);
491
385
  console.log("\n\u2713 DiffPrism configured for Claude Code.\n");
492
- console.log("Next steps:");
493
- console.log(" 1. Restart Claude Code to pick up the MCP configuration");
494
- console.log(" 2. Use /review in Claude Code to review your changes\n");
495
- console.log("Tip: Run `diffprism start` to combine setup + live watch mode.");
386
+ console.log("Next: Restart Claude Code, then type /review to review your changes.\n");
496
387
  }
497
388
  return result;
498
389
  }
@@ -600,20 +491,6 @@ function teardownClaudePermissions(baseDir) {
600
491
  writeJsonFile(filePath, existing);
601
492
  return { action: "removed", filePath };
602
493
  }
603
- function teardownHooks(gitRoot) {
604
- const filePath = path2.join(gitRoot, ".claude", "settings.json");
605
- const result = cleanDiffprismHooks(gitRoot);
606
- if (result.removed > 0) {
607
- const existing = readJsonFile(filePath);
608
- const hooks = existing.hooks;
609
- if (hooks && Object.keys(hooks).length === 0) {
610
- delete existing.hooks;
611
- writeJsonFile(filePath, existing);
612
- }
613
- return { action: "removed", filePath: filePath + " (hooks)" };
614
- }
615
- return { action: "skipped", filePath: filePath + " (hooks)" };
616
- }
617
494
  function cleanupSettingsFile(baseDir) {
618
495
  const filePath = path2.join(baseDir, ".claude", "settings.json");
619
496
  if (!fs2.existsSync(filePath)) return;
@@ -709,8 +586,6 @@ async function teardown(flags) {
709
586
  result[mcp.action].push(mcp.filePath);
710
587
  const perms = teardownClaudePermissions(gitRoot);
711
588
  result[perms.action].push(perms.filePath);
712
- const hooks = teardownHooks(gitRoot);
713
- result[hooks.action].push(hooks.filePath);
714
589
  cleanupSettingsFile(gitRoot);
715
590
  const skill = teardownSkill(gitRoot, false);
716
591
  result[skill.action].push(skill.filePath);
@@ -739,106 +614,6 @@ function printTeardownSummary(result, baseDir) {
739
614
  }
740
615
  }
741
616
 
742
- // cli/src/commands/start.ts
743
- async function start(ref, flags) {
744
- const outcome = await setup({
745
- global: flags.global,
746
- force: flags.force,
747
- quiet: true
748
- });
749
- const hasChanges = outcome.created.length > 0 || outcome.updated.length > 0;
750
- if (hasChanges) {
751
- console.log("DiffPrism configured for Claude Code.");
752
- }
753
- let diffRef;
754
- if (flags.staged) {
755
- diffRef = "staged";
756
- } else if (flags.unstaged) {
757
- diffRef = "unstaged";
758
- } else if (ref) {
759
- diffRef = ref;
760
- } else {
761
- diffRef = "working-copy";
762
- }
763
- try {
764
- const serverInfo = await ensureServer({ dev: flags.dev });
765
- console.log(
766
- `DiffPrism server at http://localhost:${serverInfo.httpPort}`
767
- );
768
- if (hasChanges) {
769
- console.log(
770
- "If this is your first time, restart Claude Code first to load the MCP server."
771
- );
772
- }
773
- const { result } = await submitReviewToServer(serverInfo, diffRef, {
774
- title: flags.title,
775
- cwd: process.cwd(),
776
- diffRef
777
- });
778
- console.log(JSON.stringify(result, null, 2));
779
- process.exit(0);
780
- } catch (err) {
781
- const message = err instanceof Error ? err.message : String(err);
782
- console.error(`Error: ${message}`);
783
- process.exit(1);
784
- }
785
- }
786
-
787
- // cli/src/commands/watch.ts
788
- async function watch(ref, flags) {
789
- let diffRef;
790
- if (flags.staged) {
791
- diffRef = "staged";
792
- } else if (flags.unstaged) {
793
- diffRef = "unstaged";
794
- } else if (ref) {
795
- diffRef = ref;
796
- } else {
797
- diffRef = "working-copy";
798
- }
799
- try {
800
- const serverInfo = await ensureServer({ dev: flags.dev });
801
- console.log(
802
- `DiffPrism server at http://localhost:${serverInfo.httpPort}`
803
- );
804
- console.log("Submitting review session...");
805
- const { result } = await submitReviewToServer(serverInfo, diffRef, {
806
- title: flags.title,
807
- cwd: process.cwd(),
808
- diffRef
809
- });
810
- console.log(JSON.stringify(result, null, 2));
811
- process.exit(0);
812
- } catch (err) {
813
- const message = err instanceof Error ? err.message : String(err);
814
- console.error(`Error: ${message}`);
815
- process.exit(1);
816
- }
817
- }
818
-
819
- // cli/src/commands/notify-stop.ts
820
- async function notifyStop() {
821
- try {
822
- const serverInfo = await isServerAlive();
823
- if (!serverInfo) {
824
- process.exit(0);
825
- return;
826
- }
827
- const controller = new AbortController();
828
- const timeout = setTimeout(() => controller.abort(), 2e3);
829
- try {
830
- await fetch(`http://localhost:${serverInfo.httpPort}/api/refresh`, {
831
- method: "POST",
832
- signal: controller.signal
833
- });
834
- } finally {
835
- clearTimeout(timeout);
836
- }
837
- } catch {
838
- }
839
- process.exit(0);
840
- }
841
-
842
617
  // cli/src/commands/server.ts
843
618
  import { spawn } from "child_process";
844
619
  import fs3 from "fs";
@@ -986,15 +761,12 @@ async function serverStop() {
986
761
 
987
762
  // cli/src/index.ts
988
763
  var program = new Command();
989
- program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.37.0" : "0.0.0-dev");
764
+ program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.37.1" : "0.0.0-dev");
990
765
  program.command("demo").description("Open a sample review to see DiffPrism in action").option("--dev", "Use Vite dev server").action(demo);
991
766
  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);
992
767
  program.command("review-pr <pr>").description("Review a GitHub pull request in DiffPrism").option("-t, --title <title>", "Override review title").option("--reasoning <text>", "Agent reasoning about the PR").option("--dev", "Use Vite dev server with HMR instead of static files").option("--post-to-github", "Automatically post review back to GitHub without prompting").action(reviewPr);
993
- 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);
994
- 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);
995
- program.command("notify-stop").description("Signal the watch server to refresh (used by Claude Code hooks)").action(notifyStop);
996
768
  program.command("serve").description("Start the MCP server for Claude Code integration").action(serve);
997
- program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Configure globally (skill + permissions, no git repo required)").option("--force", "Overwrite existing configuration files").option("--dev", "Use Vite dev server").action((flags) => {
769
+ program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Configure globally (skill + permissions, no git repo required)").option("--force", "Overwrite existing configuration files").option("--dev", "Use Vite dev server").option("--no-demo", "Skip the demo review after setup").action((flags) => {
998
770
  setup(flags);
999
771
  });
1000
772
  program.command("teardown").description("Remove DiffPrism configuration from the current project").option("--global", "Remove global configuration (skill + permissions at ~/.claude/)").option("-q, --quiet", "Suppress output").action((flags) => {
@@ -29,7 +29,7 @@ var lastGlobalServerInfo = null;
29
29
  async function startMcpServer() {
30
30
  const server = new McpServer({
31
31
  name: "diffprism",
32
- version: true ? "0.37.0" : "0.0.0-dev"
32
+ version: true ? "0.37.1" : "0.0.0-dev"
33
33
  });
34
34
  server.tool(
35
35
  "open_review",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {