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 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-QB2PKDLU.js";
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 = "all";
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
- - Tell the user: "DiffPrism watch is running \u2014 pushed reasoning to the live review."
70
- - Skip the remaining steps
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
- ### 1b. Check for Pending Review Feedback
77
+ ### 2. Load Configuration
73
78
 
74
- 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:
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 | all",
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\`: \`"all"\`
91
+ - \`defaultDiffScope\`: \`"working-copy"\`
97
92
  - \`includeReasoning\`: \`true\`
98
93
 
99
- ### 3. First-Run Onboarding
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
- ### 5. Handle the Result
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
- ### 6. Error Handling
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 setup\` to set it up, then restart Claude Code."
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
- ## Watch Mode: Polling for Review Feedback
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
- When \`diffprism watch\` is active (detected via \`.diffprism/watch.json\`), the developer can submit reviews at any time in the browser. Since there is no push notification, **you must poll for feedback** to close the loop.
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
- **After pushing context to a watch session**, call \`mcp__diffprism__get_review_result\` to check for pending feedback:
146
- - **Between tasks** \u2014 Before starting a new piece of work, check for feedback.
147
- - **After making changes** \u2014 After addressing requested changes, push updated reasoning via \`update_review_context\`, then check again shortly after.
148
- - **When the user mentions review feedback** \u2014 If the user says they submitted a review or left comments, check immediately.
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
- Do not poll in a tight loop. Check at natural breakpoints in your workflow (e.g., after finishing a subtask, before committing, before moving to the next file).
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
- - To re-run onboarding, the user can delete \`diffprism.config.json\` and invoke \`/review\` again.
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
- console.log("Setting up DiffPrism for Claude Code...\n");
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 (result.created.length > 0) {
376
- console.log("Created:");
377
- for (const f of result.created) {
378
- console.log(` + ${path.relative(gitRoot, f)}`);
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
- if (result.updated.length > 0) {
382
- console.log("Updated:");
383
- for (const f of result.updated) {
384
- console.log(` ~ ${path.relative(gitRoot, f)}`);
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
- if (result.skipped.length > 0) {
388
- console.log("Skipped (already configured):");
389
- for (const f of result.skipped) {
390
- console.log(` - ${path.relative(gitRoot, f)}`);
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 = "all";
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.13.8" : "0.0.0-dev");
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(setup);
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();