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 +13 -15
- package/dist/bin.js +22 -250
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# DiffPrism
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
npx diffprism demo
|
|
9
|
-
```
|
|
11
|
+
## How It Works
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
1. Type `/review` in Claude Code — your 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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
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
|
|
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.
|
|
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) => {
|
package/dist/mcp-server.js
CHANGED