diffprism 0.31.1 → 0.33.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,12 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ createGitHubClient,
4
+ fetchPullRequest,
5
+ fetchPullRequestDiff,
3
6
  isServerAlive,
7
+ normalizePr,
8
+ parsePrRef,
4
9
  readServerFile,
5
10
  readWatchFile,
11
+ resolveGitHubToken,
6
12
  startGlobalServer,
7
13
  startReview,
8
- startWatch
9
- } from "./chunk-OJ723D6Z.js";
14
+ startWatch,
15
+ submitGitHubReview
16
+ } from "./chunk-ZLIUNVTW.js";
10
17
 
11
18
  // cli/src/index.ts
12
19
  import { Command } from "commander";
@@ -39,6 +46,111 @@ async function review(ref, flags) {
39
46
  }
40
47
  }
41
48
 
49
+ // cli/src/commands/review-pr.ts
50
+ import readline from "readline";
51
+ async function reviewPr(pr, flags) {
52
+ try {
53
+ const token = resolveGitHubToken();
54
+ const { owner, repo, number } = parsePrRef(pr);
55
+ console.log(`Fetching PR #${number} from ${owner}/${repo}...`);
56
+ const client = createGitHubClient(token);
57
+ const [prMetadata, rawDiff] = await Promise.all([
58
+ fetchPullRequest(client, owner, repo, number),
59
+ fetchPullRequestDiff(client, owner, repo, number)
60
+ ]);
61
+ if (!rawDiff.trim()) {
62
+ console.log("PR has no changes to review.");
63
+ return;
64
+ }
65
+ const { payload, diffSet, briefing, metadata } = normalizePr(rawDiff, prMetadata, {
66
+ title: flags.title,
67
+ reasoning: flags.reasoning
68
+ });
69
+ console.log(
70
+ `${diffSet.files.length} files, +${diffSet.files.reduce((s, f) => s + f.additions, 0)} -${diffSet.files.reduce((s, f) => s + f.deletions, 0)}`
71
+ );
72
+ let result;
73
+ const serverInfo = await isServerAlive();
74
+ if (serverInfo) {
75
+ const createResponse = await fetch(
76
+ `http://localhost:${serverInfo.httpPort}/api/reviews`,
77
+ {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({
81
+ payload,
82
+ projectPath: `github:${owner}/${repo}`,
83
+ diffRef: `PR #${number}`
84
+ })
85
+ }
86
+ );
87
+ if (!createResponse.ok) {
88
+ throw new Error(`Global server returned ${createResponse.status}`);
89
+ }
90
+ const { sessionId } = await createResponse.json();
91
+ console.log(`Review session created: ${sessionId}`);
92
+ console.log("Waiting for review submission...");
93
+ result = await pollForResult(serverInfo.httpPort, sessionId);
94
+ } else {
95
+ result = await startReview({
96
+ diffRef: `PR #${number}`,
97
+ title: metadata.title,
98
+ description: metadata.description,
99
+ reasoning: metadata.reasoning,
100
+ cwd: process.cwd(),
101
+ dev: flags.dev,
102
+ injectedPayload: payload
103
+ });
104
+ }
105
+ console.log(JSON.stringify(result, null, 2));
106
+ if (flags.postToGithub || result.decision !== "dismissed" && await promptPostToGithub()) {
107
+ console.log("Posting review to GitHub...");
108
+ const posted = await submitGitHubReview(client, owner, repo, number, result);
109
+ if (posted) {
110
+ console.log(`Review posted: ${prMetadata.url}#pullrequestreview-${posted.reviewId}`);
111
+ }
112
+ }
113
+ process.exit(0);
114
+ } catch (err) {
115
+ const message = err instanceof Error ? err.message : String(err);
116
+ console.error(`Error: ${message}`);
117
+ process.exit(1);
118
+ }
119
+ }
120
+ async function pollForResult(httpPort, sessionId) {
121
+ const pollIntervalMs = 2e3;
122
+ const maxWaitMs = 600 * 1e3;
123
+ const start2 = Date.now();
124
+ while (Date.now() - start2 < maxWaitMs) {
125
+ const response = await fetch(
126
+ `http://localhost:${httpPort}/api/reviews/${sessionId}/result`
127
+ );
128
+ if (response.ok) {
129
+ const data = await response.json();
130
+ if (data.result) {
131
+ return data.result;
132
+ }
133
+ }
134
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
135
+ }
136
+ throw new Error("Review timed out waiting for submission.");
137
+ }
138
+ async function promptPostToGithub() {
139
+ if (!process.stdin.isTTY) {
140
+ return false;
141
+ }
142
+ const rl = readline.createInterface({
143
+ input: process.stdin,
144
+ output: process.stdout
145
+ });
146
+ return new Promise((resolve) => {
147
+ rl.question("Post this review to GitHub? (y/N) ", (answer) => {
148
+ rl.close();
149
+ resolve(answer.toLowerCase() === "y");
150
+ });
151
+ });
152
+ }
153
+
42
154
  // cli/src/commands/serve.ts
43
155
  async function serve() {
44
156
  const { startMcpServer } = await import("./mcp-server.js");
@@ -49,7 +161,7 @@ async function serve() {
49
161
  import fs from "fs";
50
162
  import path from "path";
51
163
  import os from "os";
52
- import readline from "readline";
164
+ import readline2 from "readline";
53
165
 
54
166
  // cli/src/templates/skill.ts
55
167
  var skillContent = `---
@@ -244,7 +356,8 @@ function setupClaudeSettings(baseDir, force) {
244
356
  "mcp__diffprism__analyze_diff",
245
357
  "mcp__diffprism__add_annotation",
246
358
  "mcp__diffprism__get_review_state",
247
- "mcp__diffprism__flag_for_attention"
359
+ "mcp__diffprism__flag_for_attention",
360
+ "mcp__diffprism__review_pr"
248
361
  ];
249
362
  const allPresent = toolNames.every((t) => allow.includes(t));
250
363
  if (allPresent && !force) {
@@ -344,7 +457,7 @@ function setupStopHook(gitRoot, force) {
344
457
  return { action, filePath: filePath + " (Stop hook)" };
345
458
  }
346
459
  async function promptUser(question) {
347
- const rl = readline.createInterface({
460
+ const rl = readline2.createInterface({
348
461
  input: process.stdin,
349
462
  output: process.stdout
350
463
  });
@@ -479,7 +592,8 @@ function isGlobalSetupDone() {
479
592
  "mcp__diffprism__analyze_diff",
480
593
  "mcp__diffprism__add_annotation",
481
594
  "mcp__diffprism__get_review_state",
482
- "mcp__diffprism__flag_for_attention"
595
+ "mcp__diffprism__flag_for_attention",
596
+ "mcp__diffprism__review_pr"
483
597
  ];
484
598
  return toolNames.every((t) => allow.includes(t));
485
599
  }
@@ -887,8 +1001,9 @@ async function serverStop() {
887
1001
 
888
1002
  // cli/src/index.ts
889
1003
  var program = new Command();
890
- program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.31.1" : "0.0.0-dev");
1004
+ program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.33.0" : "0.0.0-dev");
891
1005
  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);
1006
+ 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);
892
1007
  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);
893
1008
  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);
894
1009
  program.command("notify-stop").description("Signal the watch server to refresh (used by Claude Code hooks)").action(notifyStop);