diffprism 0.13.8 → 0.15.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/dist/bin.js +199 -78
- package/dist/{chunk-QB2PKDLU.js → chunk-NGHUHDAM.js} +454 -22
- package/dist/mcp-server.js +37 -6
- package/package.json +1 -1
- package/ui-dist/assets/index-CKJwY3F0.js +244 -0
- package/ui-dist/assets/index-D39rVNSs.css +1 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-CEmIfNA9.css +0 -1
- package/ui-dist/assets/index-DiJ_eCA-.js +0 -239
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
isServerAlive,
|
|
4
|
+
readServerFile,
|
|
3
5
|
readWatchFile,
|
|
6
|
+
startGlobalServer,
|
|
4
7
|
startReview,
|
|
5
8
|
startWatch
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NGHUHDAM.js";
|
|
7
10
|
|
|
8
11
|
// cli/src/index.ts
|
|
9
12
|
import { Command } from "commander";
|
|
@@ -18,7 +21,7 @@ async function review(ref, flags) {
|
|
|
18
21
|
} else if (ref) {
|
|
19
22
|
diffRef = ref;
|
|
20
23
|
} else {
|
|
21
|
-
diffRef = "
|
|
24
|
+
diffRef = "working-copy";
|
|
22
25
|
}
|
|
23
26
|
try {
|
|
24
27
|
const result = await startReview({
|
|
@@ -66,57 +69,29 @@ Before opening a new review, check if \`diffprism watch\` is already running. Lo
|
|
|
66
69
|
|
|
67
70
|
- **Do NOT call \`open_review\`** (the browser is already open with live-updating diffs)
|
|
68
71
|
- Instead, call \`mcp__diffprism__update_review_context\` to push your reasoning to the existing watch session
|
|
69
|
-
-
|
|
70
|
-
-
|
|
72
|
+
- Then **immediately** call \`mcp__diffprism__get_review_result\` with \`wait: true\` to block until the developer submits their review
|
|
73
|
+
- Tell the user: "DiffPrism watch is running \u2014 pushed reasoning to the live review. Waiting for your feedback..."
|
|
74
|
+
- When the result comes back, handle it per step 5 below
|
|
75
|
+
- Skip steps 2-4
|
|
71
76
|
|
|
72
|
-
###
|
|
77
|
+
### 2. Load Configuration
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **\`approved\`** \u2014 Acknowledge approval and continue with your current task.
|
|
77
|
-
- **\`approved_with_comments\`** \u2014 Note the comments, address any actionable feedback.
|
|
78
|
-
- **\`changes_requested\`** \u2014 Read the comments carefully, make the requested changes, then push updated reasoning via \`mcp__diffprism__update_review_context\`.
|
|
79
|
-
|
|
80
|
-
If no pending result, continue normally.
|
|
81
|
-
|
|
82
|
-
### 2. Check for Configuration
|
|
83
|
-
|
|
84
|
-
Look for \`diffprism.config.json\` at the project root. If it exists, read it for preferences:
|
|
79
|
+
Look for \`diffprism.config.json\` at the project root. If it exists, read it for preferences. If it doesn't exist, use defaults silently \u2014 do not prompt or create the file.
|
|
85
80
|
|
|
86
81
|
\`\`\`json
|
|
87
82
|
{
|
|
88
83
|
"reviewTrigger": "ask | before_commit | always",
|
|
89
|
-
"defaultDiffScope": "staged | unstaged |
|
|
84
|
+
"defaultDiffScope": "staged | unstaged | working-copy",
|
|
90
85
|
"includeReasoning": true | false
|
|
91
86
|
}
|
|
92
87
|
\`\`\`
|
|
93
88
|
|
|
94
89
|
**Defaults** (when fields are missing or file doesn't exist):
|
|
95
90
|
- \`reviewTrigger\`: \`"ask"\`
|
|
96
|
-
- \`defaultDiffScope\`: \`"
|
|
91
|
+
- \`defaultDiffScope\`: \`"working-copy"\`
|
|
97
92
|
- \`includeReasoning\`: \`true\`
|
|
98
93
|
|
|
99
|
-
### 3.
|
|
100
|
-
|
|
101
|
-
If \`diffprism.config.json\` does **not** exist, ask the user these questions before proceeding:
|
|
102
|
-
|
|
103
|
-
1. **"When should I open DiffPrism reviews?"**
|
|
104
|
-
- \`"ask"\` \u2014 Only when you explicitly ask (default)
|
|
105
|
-
- \`"before_commit"\` \u2014 Automatically before every commit
|
|
106
|
-
- \`"always"\` \u2014 After every code change
|
|
107
|
-
|
|
108
|
-
2. **"What should the default diff scope be?"**
|
|
109
|
-
- \`"all"\` \u2014 All changes, staged and unstaged (default)
|
|
110
|
-
- \`"staged"\` \u2014 Only staged changes
|
|
111
|
-
- \`"unstaged"\` \u2014 Only unstaged changes
|
|
112
|
-
|
|
113
|
-
3. **"Should I include my reasoning about the changes in reviews?"**
|
|
114
|
-
- Yes (default)
|
|
115
|
-
- No
|
|
116
|
-
|
|
117
|
-
After collecting answers, create \`diffprism.config.json\` at the project root with the user's choices. Then proceed to open the review.
|
|
118
|
-
|
|
119
|
-
### 4. Open the Review
|
|
94
|
+
### 3. Open the Review
|
|
120
95
|
|
|
121
96
|
Call \`mcp__diffprism__open_review\` with:
|
|
122
97
|
|
|
@@ -125,7 +100,7 @@ Call \`mcp__diffprism__open_review\` with:
|
|
|
125
100
|
- \`description\`: A brief description of what changed and why.
|
|
126
101
|
- \`reasoning\`: If \`includeReasoning\` is \`true\`, include your reasoning about the implementation decisions.
|
|
127
102
|
|
|
128
|
-
###
|
|
103
|
+
### 4. Handle the Result
|
|
129
104
|
|
|
130
105
|
The tool blocks until the user submits their review in the browser. When it returns:
|
|
131
106
|
|
|
@@ -133,21 +108,24 @@ The tool blocks until the user submits their review in the browser. When it retu
|
|
|
133
108
|
- **\`approved_with_comments\`** \u2014 Note the comments, address any actionable feedback.
|
|
134
109
|
- **\`changes_requested\`** \u2014 Read the comments carefully, make the requested changes, and offer to open another review.
|
|
135
110
|
|
|
136
|
-
###
|
|
111
|
+
### 5. Error Handling
|
|
137
112
|
|
|
138
113
|
If the \`mcp__diffprism__open_review\` tool is not available:
|
|
139
|
-
- Tell the user: "The DiffPrism MCP server isn't configured. Run \`npx diffprism
|
|
114
|
+
- Tell the user: "The DiffPrism MCP server isn't configured. Run \`npx diffprism start\` to set it up, then restart Claude Code."
|
|
115
|
+
|
|
116
|
+
## Watch Mode: Waiting for Review Feedback
|
|
140
117
|
|
|
141
|
-
|
|
118
|
+
When \`diffprism watch\` is active (detected via \`.diffprism/watch.json\`), the developer can submit reviews at any time in the browser.
|
|
142
119
|
|
|
143
|
-
|
|
120
|
+
**After pushing context to a watch session**, call \`mcp__diffprism__get_review_result\` with \`wait: true\` to block until the developer submits their review. This polls the result file every 2 seconds and returns as soon as feedback is available (up to 5 minutes by default).
|
|
144
121
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
122
|
+
Use this pattern:
|
|
123
|
+
1. Push context via \`update_review_context\`
|
|
124
|
+
2. Call \`get_review_result\` with \`wait: true\` \u2014 this blocks until the developer submits
|
|
125
|
+
3. Handle the result (approved, changes_requested, etc.)
|
|
126
|
+
4. If changes were requested, make fixes, push updated context, and call \`get_review_result\` with \`wait: true\` again
|
|
149
127
|
|
|
150
|
-
|
|
128
|
+
You can also check for feedback without blocking by calling \`get_review_result\` without \`wait\` at natural breakpoints (between tasks, before committing, etc.).
|
|
151
129
|
|
|
152
130
|
## Behavior Rules
|
|
153
131
|
|
|
@@ -156,7 +134,7 @@ Do not poll in a tight loop. Check at natural breakpoints in your workflow (e.g.
|
|
|
156
134
|
- \`"ask"\` \u2014 Never auto-review; only review when the user asks.
|
|
157
135
|
- \`"before_commit"\` \u2014 Open a review before creating any git commit.
|
|
158
136
|
- \`"always"\` \u2014 Open a review after any code change.
|
|
159
|
-
-
|
|
137
|
+
- Power users can create \`diffprism.config.json\` manually to customize defaults.
|
|
160
138
|
`;
|
|
161
139
|
|
|
162
140
|
// cli/src/commands/setup.ts
|
|
@@ -352,11 +330,14 @@ async function setup(flags) {
|
|
|
352
330
|
"Error: Not in a git repository. Run this command from inside a git project."
|
|
353
331
|
);
|
|
354
332
|
process.exit(1);
|
|
355
|
-
return;
|
|
333
|
+
return { created: [], updated: [], skipped: [] };
|
|
356
334
|
}
|
|
357
335
|
const force = flags.force ?? false;
|
|
358
336
|
const global = flags.global ?? false;
|
|
359
|
-
|
|
337
|
+
const quiet = flags.quiet ?? false;
|
|
338
|
+
if (!quiet) {
|
|
339
|
+
console.log("Setting up DiffPrism for Claude Code...\n");
|
|
340
|
+
}
|
|
360
341
|
const result = { created: [], updated: [], skipped: [] };
|
|
361
342
|
const gitignore = await setupGitignore(gitRoot);
|
|
362
343
|
result[gitignore.action].push(gitignore.filePath);
|
|
@@ -365,40 +346,91 @@ async function setup(flags) {
|
|
|
365
346
|
const settings = setupClaudeSettings(gitRoot, force);
|
|
366
347
|
result[settings.action === "skipped" ? "skipped" : settings.action === "created" ? "created" : "updated"].push(settings.filePath);
|
|
367
348
|
const cleaned = cleanDiffprismHooks(gitRoot);
|
|
368
|
-
if (cleaned.removed > 0) {
|
|
349
|
+
if (cleaned.removed > 0 && !quiet) {
|
|
369
350
|
console.log(` Cleaned ${cleaned.removed} stale hook(s)`);
|
|
370
351
|
}
|
|
371
352
|
const hook = setupStopHook(gitRoot, force);
|
|
372
353
|
result[hook.action === "skipped" ? "skipped" : hook.action === "created" ? "created" : "updated"].push(hook.filePath);
|
|
373
354
|
const skill = setupSkill(gitRoot, global, force);
|
|
374
355
|
result[skill.action === "skipped" ? "skipped" : skill.action === "created" ? "created" : "updated"].push(skill.filePath);
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
356
|
+
if (!quiet) {
|
|
357
|
+
if (result.created.length > 0) {
|
|
358
|
+
console.log("Created:");
|
|
359
|
+
for (const f of result.created) {
|
|
360
|
+
console.log(` + ${path.relative(gitRoot, f)}`);
|
|
361
|
+
}
|
|
379
362
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
363
|
+
if (result.updated.length > 0) {
|
|
364
|
+
console.log("Updated:");
|
|
365
|
+
for (const f of result.updated) {
|
|
366
|
+
console.log(` ~ ${path.relative(gitRoot, f)}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (result.skipped.length > 0) {
|
|
370
|
+
console.log("Skipped (already configured):");
|
|
371
|
+
for (const f of result.skipped) {
|
|
372
|
+
console.log(` - ${path.relative(gitRoot, f)}`);
|
|
373
|
+
}
|
|
385
374
|
}
|
|
375
|
+
console.log("\n\u2713 DiffPrism configured for Claude Code.\n");
|
|
376
|
+
console.log("Next steps:");
|
|
377
|
+
console.log(" 1. Restart Claude Code to pick up the MCP configuration");
|
|
378
|
+
console.log(" 2. Use /review in Claude Code to review your changes\n");
|
|
379
|
+
console.log("Tip: Run `diffprism start` to combine setup + live watch mode.");
|
|
386
380
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// cli/src/commands/start.ts
|
|
385
|
+
async function start(ref, flags) {
|
|
386
|
+
const outcome = await setup({
|
|
387
|
+
global: flags.global,
|
|
388
|
+
force: flags.force,
|
|
389
|
+
quiet: true
|
|
390
|
+
});
|
|
391
|
+
const hasChanges = outcome.created.length > 0 || outcome.updated.length > 0;
|
|
392
|
+
if (hasChanges) {
|
|
393
|
+
console.log("\u2713 DiffPrism configured for Claude Code.");
|
|
394
|
+
}
|
|
395
|
+
let diffRef;
|
|
396
|
+
if (flags.staged) {
|
|
397
|
+
diffRef = "staged";
|
|
398
|
+
} else if (flags.unstaged) {
|
|
399
|
+
diffRef = "unstaged";
|
|
400
|
+
} else if (ref) {
|
|
401
|
+
diffRef = ref;
|
|
402
|
+
} else {
|
|
403
|
+
diffRef = "working-copy";
|
|
404
|
+
}
|
|
405
|
+
const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
|
|
406
|
+
try {
|
|
407
|
+
const handle = await startWatch({
|
|
408
|
+
diffRef,
|
|
409
|
+
title: flags.title,
|
|
410
|
+
cwd: process.cwd(),
|
|
411
|
+
dev: flags.dev,
|
|
412
|
+
pollInterval
|
|
413
|
+
});
|
|
414
|
+
console.log("Use /review in Claude Code to send changes for review.");
|
|
415
|
+
if (hasChanges) {
|
|
416
|
+
console.log(
|
|
417
|
+
"If this is your first time, restart Claude Code first to load the MCP server."
|
|
418
|
+
);
|
|
391
419
|
}
|
|
420
|
+
const shutdown = async () => {
|
|
421
|
+
console.log("\nStopping DiffPrism...");
|
|
422
|
+
await handle.stop();
|
|
423
|
+
process.exit(0);
|
|
424
|
+
};
|
|
425
|
+
process.on("SIGINT", shutdown);
|
|
426
|
+
process.on("SIGTERM", shutdown);
|
|
427
|
+
await new Promise(() => {
|
|
428
|
+
});
|
|
429
|
+
} catch (err) {
|
|
430
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
431
|
+
console.error(`Error: ${message}`);
|
|
432
|
+
process.exit(1);
|
|
392
433
|
}
|
|
393
|
-
console.log(
|
|
394
|
-
"\nYou can now use /review in Claude Code to open a DiffPrism review."
|
|
395
|
-
);
|
|
396
|
-
console.log(
|
|
397
|
-
"Or run `diffprism watch --staged` for live-updating reviews."
|
|
398
|
-
);
|
|
399
|
-
console.log(
|
|
400
|
-
"If Claude Code is running, restart it to pick up the new configuration."
|
|
401
|
-
);
|
|
402
434
|
}
|
|
403
435
|
|
|
404
436
|
// cli/src/commands/watch.ts
|
|
@@ -411,7 +443,7 @@ async function watch(ref, flags) {
|
|
|
411
443
|
} else if (ref) {
|
|
412
444
|
diffRef = ref;
|
|
413
445
|
} else {
|
|
414
|
-
diffRef = "
|
|
446
|
+
diffRef = "working-copy";
|
|
415
447
|
}
|
|
416
448
|
const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
|
|
417
449
|
try {
|
|
@@ -461,12 +493,101 @@ async function notifyStop() {
|
|
|
461
493
|
process.exit(0);
|
|
462
494
|
}
|
|
463
495
|
|
|
496
|
+
// cli/src/commands/server.ts
|
|
497
|
+
async function server(flags) {
|
|
498
|
+
const existing = await isServerAlive();
|
|
499
|
+
if (existing) {
|
|
500
|
+
console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
|
|
501
|
+
console.log(`Use 'diffprism server stop' to stop it first.`);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const httpPort = flags.port ? parseInt(flags.port, 10) : void 0;
|
|
506
|
+
const wsPort = flags.wsPort ? parseInt(flags.wsPort, 10) : void 0;
|
|
507
|
+
try {
|
|
508
|
+
const handle = await startGlobalServer({
|
|
509
|
+
httpPort,
|
|
510
|
+
wsPort,
|
|
511
|
+
dev: flags.dev
|
|
512
|
+
});
|
|
513
|
+
const shutdown = async () => {
|
|
514
|
+
console.log("\nStopping server...");
|
|
515
|
+
await handle.stop();
|
|
516
|
+
process.exit(0);
|
|
517
|
+
};
|
|
518
|
+
process.on("SIGINT", shutdown);
|
|
519
|
+
process.on("SIGTERM", shutdown);
|
|
520
|
+
await new Promise(() => {
|
|
521
|
+
});
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
524
|
+
console.error(`Error starting server: ${message}`);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function serverStatus() {
|
|
529
|
+
const info = await isServerAlive();
|
|
530
|
+
if (!info) {
|
|
531
|
+
console.log("No DiffPrism server is running.");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
|
|
537
|
+
signal: AbortSignal.timeout(2e3)
|
|
538
|
+
});
|
|
539
|
+
const status = await response.json();
|
|
540
|
+
console.log(`DiffPrism Server`);
|
|
541
|
+
console.log(` API: http://localhost:${info.httpPort}`);
|
|
542
|
+
console.log(` WS: ws://localhost:${info.wsPort}`);
|
|
543
|
+
console.log(` PID: ${status.pid}`);
|
|
544
|
+
console.log(` Sessions: ${status.sessions}`);
|
|
545
|
+
console.log(` Uptime: ${Math.floor(status.uptime)}s`);
|
|
546
|
+
if (status.sessions > 0) {
|
|
547
|
+
const sessionsResponse = await fetch(
|
|
548
|
+
`http://localhost:${info.httpPort}/api/reviews`,
|
|
549
|
+
{ signal: AbortSignal.timeout(2e3) }
|
|
550
|
+
);
|
|
551
|
+
const { sessions } = await sessionsResponse.json();
|
|
552
|
+
console.log(`
|
|
553
|
+
Active Sessions:`);
|
|
554
|
+
for (const s of sessions) {
|
|
555
|
+
const label = s.title ?? s.branch ?? s.projectPath;
|
|
556
|
+
console.log(` ${s.id} \u2014 ${label} (${s.status}, ${s.fileCount} files, +${s.additions}/-${s.deletions})`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} catch (err) {
|
|
560
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
561
|
+
console.error(`Error checking server status: ${message}`);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function serverStop() {
|
|
566
|
+
const info = readServerFile();
|
|
567
|
+
if (!info) {
|
|
568
|
+
console.log("No DiffPrism server is running.");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
process.kill(info.pid, "SIGTERM");
|
|
573
|
+
console.log(`Sent stop signal to DiffPrism server (PID ${info.pid}).`);
|
|
574
|
+
} catch {
|
|
575
|
+
console.log(`Server process (PID ${info.pid}) is no longer running. Cleaning up.`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
464
579
|
// cli/src/index.ts
|
|
465
580
|
var program = new Command();
|
|
466
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
581
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.15.0" : "0.0.0-dev");
|
|
467
582
|
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);
|
|
583
|
+
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);
|
|
468
584
|
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);
|
|
469
585
|
program.command("notify-stop").description("Signal the watch server to refresh (used by Claude Code hooks)").action(notifyStop);
|
|
470
586
|
program.command("serve").description("Start the MCP server for Claude Code integration").action(serve);
|
|
471
|
-
program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action(
|
|
587
|
+
program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action((flags) => {
|
|
588
|
+
setup(flags);
|
|
589
|
+
});
|
|
590
|
+
var serverCmd = program.command("server").description("Start the global DiffPrism server for multi-session reviews").option("-p, --port <port>", "HTTP API port (default: 24680)").option("--ws-port <port>", "WebSocket port (default: 24681)").option("--dev", "Use Vite dev server with HMR instead of static files").action(server);
|
|
591
|
+
serverCmd.command("status").description("Check if the global server is running and list active sessions").action(serverStatus);
|
|
592
|
+
serverCmd.command("stop").description("Stop the running global server").action(serverStop);
|
|
472
593
|
program.parse();
|