diffprism 0.44.0 → 0.45.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/README.md +101 -38
- package/dist/bin.js +61 -70
- package/dist/{chunk-4IQOTAHD.js → chunk-RICVPPTE.js} +136 -0
- package/dist/{chunk-LH6H3QSC.js → chunk-RVI74JF5.js} +1 -1
- package/dist/{demo-MDPKCAXZ.js → demo-WVNNI6CE.js} +2 -2
- package/dist/mcp-server.js +430 -72
- package/dist/src-KF5HRJPX.js +23 -0
- package/package.json +1 -1
- package/ui-dist/assets/index-AgilKYdG.css +1 -0
- package/ui-dist/assets/index-BV9fRDP_.js +335 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-DOhngwbV.css +0 -1
- package/ui-dist/assets/index-Dw_IuVjX.js +0 -335
- /package/dist/{chunk-6J6PSBL2.js → chunk-24B33UN6.js} +0 -0
package/README.md
CHANGED
|
@@ -1,67 +1,130 @@
|
|
|
1
1
|
# DiffPrism
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
are hard to read, and there's no way to leave structured feedback that the agent
|
|
5
|
-
actually acts on.
|
|
6
|
-
|
|
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.
|
|
3
|
+
Review GitHub PRs with AI superpowers. Paste a PR URL, see the diff in your browser, and use Claude Code or Cursor to interrogate every line, file, and change. Your AI gets full codebase context from your local clone — not just the diff hunks.
|
|
10
4
|
|
|
11
5
|
## How It Works
|
|
12
6
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
7
|
+
1. **Open a PR** — `diffprism review https://github.com/owner/repo/pull/123`
|
|
8
|
+
2. **See the diff** — Browser opens with syntax-highlighted diffs, file browser, and analysis briefing
|
|
9
|
+
3. **Ask your AI** — In Claude Code or Cursor, ask questions about the changes. Your AI calls MCP tools to get context and posts findings inline on the diff.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
$ cd ~/dev/my-project
|
|
13
|
+
$ diffprism review https://github.com/owner/repo/pull/123
|
|
14
|
+
Fetching PR #123 from owner/repo...
|
|
15
|
+
Add retry logic to API client
|
|
16
|
+
4 files changed
|
|
17
|
+
Local repo: /Users/you/dev/my-project
|
|
18
|
+
|
|
19
|
+
Review open in browser. Use Claude Code to ask questions about this PR.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then in Claude Code:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
> What does this PR change?
|
|
26
|
+
→ calls get_pr_context → high-level overview
|
|
27
|
+
|
|
28
|
+
> Is the retry logic in client.ts correct?
|
|
29
|
+
→ calls get_file_diff + get_file_context → full file from your local clone
|
|
30
|
+
|
|
31
|
+
> Flag line 47 as a concern
|
|
32
|
+
→ calls add_review_comment → annotation appears on the diff in your browser
|
|
33
|
+
```
|
|
16
34
|
|
|
17
|
-
## Setup
|
|
35
|
+
## Setup
|
|
18
36
|
|
|
19
37
|
```bash
|
|
20
|
-
|
|
38
|
+
npm install -g diffprism
|
|
39
|
+
diffprism setup # Register MCP server with Claude Code
|
|
21
40
|
```
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
Run the server from within your local clone so the AI gets full file context:
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
```bash
|
|
45
|
+
cd ~/dev/my-project
|
|
46
|
+
diffprism server # Or let it auto-start on first review
|
|
47
|
+
```
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
## PR Review
|
|
28
50
|
|
|
29
51
|
```bash
|
|
30
|
-
diffprism review
|
|
31
|
-
diffprism review
|
|
32
|
-
diffprism review HEAD~3 # Last 3 commits
|
|
33
|
-
diffprism review main..feature # Branch diff
|
|
52
|
+
diffprism review https://github.com/owner/repo/pull/123 # Full GitHub URL
|
|
53
|
+
diffprism review owner/repo#123 # Shorthand format
|
|
34
54
|
```
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
The server auto-detects your local clone by matching `git remote -v` against the PR's repo. Your AI can then read full files via `git show` — not just diff hunks.
|
|
57
|
+
|
|
58
|
+
## MCP Tools
|
|
59
|
+
|
|
60
|
+
DiffPrism exposes 14 MCP tools to your AI:
|
|
61
|
+
|
|
62
|
+
### PR Review
|
|
63
|
+
| Tool | Purpose |
|
|
64
|
+
|------|---------|
|
|
65
|
+
| `get_pr_context` | High-level PR overview: metadata, briefing, file list, local repo status |
|
|
66
|
+
| `get_file_diff` | Diff hunks for a specific file with triage category |
|
|
67
|
+
| `get_file_context` | Full file content from local repo via `git show` |
|
|
68
|
+
| `add_review_comment` | Post a comment that appears inline on the diff in real-time |
|
|
69
|
+
| `get_review_comments` | Read all comments and annotations on the session |
|
|
70
|
+
| `get_user_focus` | What file/line the user is currently viewing in the browser |
|
|
71
|
+
|
|
72
|
+
### Review Lifecycle
|
|
73
|
+
| Tool | Purpose |
|
|
74
|
+
|------|---------|
|
|
75
|
+
| `open_review` | Open browser review UI for local changes or a GitHub PR |
|
|
76
|
+
| `get_review_result` | Fetch result from a previous review |
|
|
77
|
+
| `update_review_context` | Push updated reasoning/description to a running session |
|
|
78
|
+
|
|
79
|
+
### Analysis
|
|
80
|
+
| Tool | Purpose |
|
|
81
|
+
|------|---------|
|
|
82
|
+
| `analyze_diff` | Returns analysis JSON (patterns, complexity, test gaps) |
|
|
83
|
+
| `get_diff` | Returns structured diff JSON (file-level and hunk-level changes) |
|
|
37
84
|
|
|
38
|
-
|
|
85
|
+
### Annotation
|
|
86
|
+
| Tool | Purpose |
|
|
87
|
+
|------|---------|
|
|
88
|
+
| `add_annotation` | Post a structured finding on a specific line |
|
|
89
|
+
| `flag_for_attention` | Mark files for human attention |
|
|
90
|
+
| `get_review_state` | Get current state of a session including all annotations |
|
|
39
91
|
|
|
40
|
-
|
|
92
|
+
## Local Agent Review
|
|
93
|
+
|
|
94
|
+
DiffPrism also works for reviewing local agent-generated changes:
|
|
41
95
|
|
|
42
96
|
```bash
|
|
43
|
-
diffprism
|
|
44
|
-
diffprism
|
|
97
|
+
diffprism review # Review all changes (staged + unstaged)
|
|
98
|
+
diffprism review --staged # Staged changes only
|
|
99
|
+
diffprism review HEAD~3 # Last 3 commits
|
|
100
|
+
diffprism review main..feature # Branch diff
|
|
45
101
|
```
|
|
46
102
|
|
|
103
|
+
Running multiple Claude Code sessions? All reviews appear in one browser dashboard with status badges, branch info, and desktop notifications.
|
|
104
|
+
|
|
47
105
|
## Features
|
|
48
106
|
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **Multi-session dashboard** —
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **Keyboard shortcuts** — `j`/`k` files, `n`/`p` hunks, `
|
|
58
|
-
- **Dark/light mode** —
|
|
107
|
+
- **AI-powered PR review** — Your AI gets full codebase context via 14 MCP tools
|
|
108
|
+
- **Live annotations** — AI findings appear inline on the diff in real-time
|
|
109
|
+
- **Local repo context** — Full file content from your clone, not just diff hunks
|
|
110
|
+
- **No vendor lock-in** — Works with Claude Code, Cursor, or any MCP client
|
|
111
|
+
- **Syntax-highlighted diffs** — Unified or split view with refractor
|
|
112
|
+
- **Multi-session dashboard** — Review multiple agents from one browser tab
|
|
113
|
+
- **Review briefing** — Complexity scores, test coverage gaps, pattern flags
|
|
114
|
+
- **Auto-detect local repo** — Matches `git remote -v` against the PR's repo
|
|
115
|
+
- **Keyboard shortcuts** — `j`/`k` files, `n`/`p` hunks, `s` status, `?` help
|
|
116
|
+
- **Dark/light mode** — Toggle with persistence
|
|
59
117
|
|
|
60
|
-
##
|
|
118
|
+
## CLI Reference
|
|
61
119
|
|
|
62
120
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
121
|
+
diffprism review <ref> # Open a review (PR URL, git ref, or flags)
|
|
122
|
+
diffprism setup # Configure Claude Code integration
|
|
123
|
+
diffprism setup --global # Global setup (no git repo needed)
|
|
124
|
+
diffprism server # Start the background server
|
|
125
|
+
diffprism server status # Check server status
|
|
126
|
+
diffprism server stop # Stop the server
|
|
127
|
+
diffprism teardown # Remove configuration
|
|
65
128
|
```
|
|
66
129
|
|
|
67
130
|
## Development
|
|
@@ -82,7 +145,7 @@ packages/core — Server, types, server-client utilities
|
|
|
82
145
|
packages/git — Git diff extraction + parser
|
|
83
146
|
packages/analysis — Deterministic review briefing
|
|
84
147
|
packages/ui — React 19 + Vite 6 + Tailwind + Zustand
|
|
85
|
-
packages/mcp-server — MCP tool server (
|
|
148
|
+
packages/mcp-server — MCP tool server (14 tools)
|
|
86
149
|
packages/github — GitHub PR fetching + review submission
|
|
87
150
|
cli/ — Commander CLI
|
|
88
151
|
```
|
package/dist/bin.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
createGitHubClient,
|
|
4
|
-
fetchPullRequest,
|
|
5
|
-
fetchPullRequestDiff,
|
|
6
3
|
isPrRef,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
resolveGitHubToken,
|
|
10
|
-
submitGitHubReview
|
|
11
|
-
} from "./chunk-6J6PSBL2.js";
|
|
4
|
+
parsePrRef
|
|
5
|
+
} from "./chunk-24B33UN6.js";
|
|
12
6
|
import {
|
|
13
7
|
demo
|
|
14
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RVI74JF5.js";
|
|
15
9
|
import {
|
|
16
10
|
ensureServer,
|
|
17
11
|
isServerAlive,
|
|
18
12
|
readServerFile,
|
|
19
13
|
startGlobalServer,
|
|
20
14
|
submitReviewToServer
|
|
21
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-RICVPPTE.js";
|
|
22
16
|
import "./chunk-QGWYCEJN.js";
|
|
23
17
|
import "./chunk-DHCVZGHE.js";
|
|
24
18
|
import "./chunk-JSBRDJBE.js";
|
|
@@ -27,7 +21,6 @@ import "./chunk-JSBRDJBE.js";
|
|
|
27
21
|
import { Command } from "commander";
|
|
28
22
|
|
|
29
23
|
// cli/src/commands/review.ts
|
|
30
|
-
import readline from "readline";
|
|
31
24
|
async function review(ref, flags) {
|
|
32
25
|
let diffRef;
|
|
33
26
|
if (flags.staged) {
|
|
@@ -63,55 +56,32 @@ async function reviewLocalFlow(diffRef, flags) {
|
|
|
63
56
|
process.exit(0);
|
|
64
57
|
}
|
|
65
58
|
async function reviewPrFlow(pr, flags) {
|
|
66
|
-
const token = resolveGitHubToken();
|
|
67
59
|
const { owner, repo, number } = parsePrRef(pr);
|
|
68
60
|
console.log(`Fetching PR #${number} from ${owner}/${repo}...`);
|
|
69
|
-
const client = createGitHubClient(token);
|
|
70
|
-
const [prMetadata, rawDiff] = await Promise.all([
|
|
71
|
-
fetchPullRequest(client, owner, repo, number),
|
|
72
|
-
fetchPullRequestDiff(client, owner, repo, number)
|
|
73
|
-
]);
|
|
74
|
-
if (!rawDiff.trim()) {
|
|
75
|
-
console.log("PR has no changes to review.");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const { payload, diffSet } = normalizePr(rawDiff, prMetadata, {
|
|
79
|
-
title: flags.title,
|
|
80
|
-
reasoning: flags.reasoning
|
|
81
|
-
});
|
|
82
|
-
console.log(
|
|
83
|
-
`${diffSet.files.length} files, +${diffSet.files.reduce((s, f) => s + f.additions, 0)} -${diffSet.files.reduce((s, f) => s + f.deletions, 0)}`
|
|
84
|
-
);
|
|
85
61
|
const serverInfo = await ensureServer({ dev: flags.dev });
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (result && (flags.postToGithub || result.decision !== "dismissed" && await promptPostToGithub())) {
|
|
93
|
-
console.log("Posting review to GitHub...");
|
|
94
|
-
const posted = await submitGitHubReview(client, owner, repo, number, result);
|
|
95
|
-
if (posted) {
|
|
96
|
-
console.log(`Review posted: ${prMetadata.url}#pullrequestreview-${posted.reviewId}`);
|
|
62
|
+
const response = await fetch(
|
|
63
|
+
`http://localhost:${serverInfo.httpPort}/api/pr/open`,
|
|
64
|
+
{
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({ prUrl: pr })
|
|
97
68
|
}
|
|
69
|
+
);
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
if (!response.ok || !data.sessionId) {
|
|
72
|
+
console.error(`Error: ${data.error ?? "Failed to open PR"}`);
|
|
73
|
+
process.exit(1);
|
|
98
74
|
}
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
console.log(`${data.pr?.title ?? `PR #${number}`}`);
|
|
76
|
+
console.log(`${data.fileCount} file${data.fileCount !== 1 ? "s" : ""} changed`);
|
|
77
|
+
if (data.localRepoPath) {
|
|
78
|
+
console.log(`Local repo: ${data.localRepoPath}`);
|
|
79
|
+
} else {
|
|
80
|
+
console.log("No local clone detected \u2014 file context unavailable");
|
|
104
81
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
return new Promise((resolve) => {
|
|
110
|
-
rl.question("Post this review to GitHub? (y/N) ", (answer) => {
|
|
111
|
-
rl.close();
|
|
112
|
-
resolve(answer.toLowerCase() === "y");
|
|
113
|
-
});
|
|
114
|
-
});
|
|
82
|
+
console.log(`
|
|
83
|
+
Review open in browser. Use Claude Code to ask questions about this PR.`);
|
|
84
|
+
console.log(`MCP tools available: get_pr_context, get_file_diff, get_file_context, add_review_comment`);
|
|
115
85
|
}
|
|
116
86
|
|
|
117
87
|
// cli/src/commands/serve.ts
|
|
@@ -124,7 +94,7 @@ async function serve() {
|
|
|
124
94
|
import fs from "fs";
|
|
125
95
|
import path from "path";
|
|
126
96
|
import os from "os";
|
|
127
|
-
import
|
|
97
|
+
import readline from "readline";
|
|
128
98
|
|
|
129
99
|
// cli/src/templates/skill.ts
|
|
130
100
|
var skillContent = `---
|
|
@@ -134,7 +104,7 @@ description: Open current code changes in DiffPrism's browser-based review UI fo
|
|
|
134
104
|
|
|
135
105
|
# DiffPrism Review
|
|
136
106
|
|
|
137
|
-
You have
|
|
107
|
+
You have 14 DiffPrism MCP tools available. Use them proactively \u2014 don't wait for the user to ask.
|
|
138
108
|
|
|
139
109
|
## Workflow 1: Self-Review Before Human Review
|
|
140
110
|
|
|
@@ -173,22 +143,32 @@ Handle the review result:
|
|
|
173
143
|
- If \`postReviewAction\` is \`"commit"\` \u2014 commit the changes.
|
|
174
144
|
- If \`postReviewAction\` is \`"commit_and_pr"\` \u2014 commit and open a PR.
|
|
175
145
|
|
|
176
|
-
## Workflow 3: PR Review
|
|
146
|
+
## Workflow 3: PR Super Review
|
|
177
147
|
|
|
178
|
-
|
|
148
|
+
When the user opens a GitHub PR for review (via \`diffprism review <PR URL>\` or the DiffPrism UI), you become their AI-powered code reviewer. The diff is visible in the browser; you provide the intelligence.
|
|
179
149
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
150
|
+
### Getting oriented
|
|
151
|
+
1. Call \`mcp__diffprism__get_pr_context\` to understand the PR: title, author, branches, file list, briefing summary, and whether a local repo is connected.
|
|
152
|
+
|
|
153
|
+
### Investigating changes
|
|
154
|
+
2. Call \`mcp__diffprism__get_file_diff\` for specific files to see their hunks and triage category (critical/notable/mechanical).
|
|
155
|
+
3. Call \`mcp__diffprism__get_file_context\` to read full files from the local repo \u2014 this gives you surrounding code, not just diff hunks. Use this to understand how changed code fits into the broader file.
|
|
156
|
+
4. Call \`mcp__diffprism__get_user_focus\` to see what file/line the user is currently viewing in the browser. Proactively offer context about what they're looking at.
|
|
157
|
+
|
|
158
|
+
### Leaving findings
|
|
159
|
+
5. Call \`mcp__diffprism__add_review_comment\` to post findings directly to the browser UI. Comments appear as inline annotations on the diff in real-time. Use this to flag issues, suggest improvements, or answer the user's questions visually.
|
|
160
|
+
6. Call \`mcp__diffprism__get_review_comments\` to see what's already been noted before adding your own.
|
|
161
|
+
|
|
162
|
+
### Key principle
|
|
163
|
+
The user sees the diff in the browser. You see it through MCP tools. Work together \u2014 they spot visual patterns, you analyze logic and context.
|
|
183
164
|
|
|
184
165
|
## Tool Reference
|
|
185
166
|
|
|
186
167
|
### Review Lifecycle
|
|
187
168
|
| Tool | Purpose |
|
|
188
169
|
|------|---------|
|
|
189
|
-
| \`open_review\` | Open browser review UI for local changes
|
|
190
|
-
| \`
|
|
191
|
-
| \`get_review_result\` | Fetch result from a previous review (advanced \u2014 \`open_review\` already returns it). |
|
|
170
|
+
| \`open_review\` | Open browser review UI for local changes or a GitHub PR. |
|
|
171
|
+
| \`get_review_result\` | Fetch result from a previous review. |
|
|
192
172
|
| \`update_review_context\` | Push updated reasoning/description to a running review session. |
|
|
193
173
|
|
|
194
174
|
### Headless Analysis
|
|
@@ -197,10 +177,20 @@ To review a GitHub pull request:
|
|
|
197
177
|
| \`analyze_diff\` | Returns analysis JSON (patterns, complexity, test gaps) without opening a browser. |
|
|
198
178
|
| \`get_diff\` | Returns structured diff JSON (file-level and hunk-level changes). |
|
|
199
179
|
|
|
200
|
-
###
|
|
180
|
+
### PR Super Review
|
|
201
181
|
| Tool | Purpose |
|
|
202
182
|
|------|---------|
|
|
203
|
-
| \`
|
|
183
|
+
| \`get_pr_context\` | High-level PR overview: metadata, briefing, file list, local repo status. |
|
|
184
|
+
| \`get_file_diff\` | Diff hunks for a specific file with triage category. |
|
|
185
|
+
| \`get_file_context\` | Full file content from local repo via \`git show\`. |
|
|
186
|
+
| \`get_user_focus\` | What file/line the user is currently viewing in the browser UI. |
|
|
187
|
+
|
|
188
|
+
### Annotation & Commenting
|
|
189
|
+
| Tool | Purpose |
|
|
190
|
+
|------|---------|
|
|
191
|
+
| \`add_review_comment\` | Post a comment that appears inline in the browser diff. |
|
|
192
|
+
| \`get_review_comments\` | Read all comments and annotations on the session. |
|
|
193
|
+
| \`add_annotation\` | Post a structured finding (finding/suggestion/question/warning). |
|
|
204
194
|
| \`flag_for_attention\` | Mark files for human attention with warning annotations. |
|
|
205
195
|
| \`get_review_state\` | Get current state of a review session including all annotations. |
|
|
206
196
|
|
|
@@ -209,6 +199,7 @@ To review a GitHub pull request:
|
|
|
209
199
|
- **Self-review is proactive** \u2014 run \`analyze_diff\` after significant changes without being asked.
|
|
210
200
|
- **Human review requires explicit request** \u2014 only open \`open_review\` when the user asks (\`/review\`, "review my changes", or as part of a defined workflow like PR creation).
|
|
211
201
|
- **Annotate generously** \u2014 the more context you provide in annotations, the faster the reviewer can make decisions.
|
|
202
|
+
- **PR review is conversational** \u2014 when a PR is open, use the super review tools to answer questions and post findings without being asked to use specific tools.
|
|
212
203
|
`;
|
|
213
204
|
|
|
214
205
|
// cli/src/commands/setup.ts
|
|
@@ -306,7 +297,7 @@ function setupSkill(gitRoot, global, force) {
|
|
|
306
297
|
return { action, filePath };
|
|
307
298
|
}
|
|
308
299
|
async function promptUser(question) {
|
|
309
|
-
const rl =
|
|
300
|
+
const rl = readline.createInterface({
|
|
310
301
|
input: process.stdin,
|
|
311
302
|
output: process.stdout
|
|
312
303
|
});
|
|
@@ -379,7 +370,7 @@ async function setupInteractive(flags) {
|
|
|
379
370
|
}
|
|
380
371
|
async function runDemo(dev) {
|
|
381
372
|
console.log("");
|
|
382
|
-
const { demo: demo2 } = await import("./demo-
|
|
373
|
+
const { demo: demo2 } = await import("./demo-WVNNI6CE.js");
|
|
383
374
|
await demo2({ dev });
|
|
384
375
|
}
|
|
385
376
|
async function setupBatch(flags) {
|
|
@@ -822,7 +813,7 @@ async function fetchStatus(httpPort) {
|
|
|
822
813
|
return await response.json();
|
|
823
814
|
}
|
|
824
815
|
function printStatus(status, httpPort) {
|
|
825
|
-
const version = true ? "0.
|
|
816
|
+
const version = true ? "0.45.0" : "0.0.0-dev";
|
|
826
817
|
console.log(`
|
|
827
818
|
DiffPrism v${version}
|
|
828
819
|
`);
|
|
@@ -862,7 +853,7 @@ async function defaultAction() {
|
|
|
862
853
|
|
|
863
854
|
// cli/src/index.ts
|
|
864
855
|
var program = new Command();
|
|
865
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
856
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.45.0" : "0.0.0-dev");
|
|
866
857
|
program.action(defaultAction);
|
|
867
858
|
program.command("demo").description("Open a sample review to see DiffPrism in action").option("--dev", "Use Vite dev server").action(demo);
|
|
868
859
|
program.command("review [ref]").description("Open a browser-based diff review (local git ref or GitHub PR ref like owner/repo#123)").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").option("--reasoning <text>", "Agent reasoning about the changes").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(review);
|
|
@@ -851,6 +851,96 @@ async function handleApiRequest(req, res) {
|
|
|
851
851
|
}
|
|
852
852
|
return true;
|
|
853
853
|
}
|
|
854
|
+
if (method === "POST" && url === "/api/pr/open") {
|
|
855
|
+
try {
|
|
856
|
+
const body = await readBody(req);
|
|
857
|
+
const { prUrl } = JSON.parse(body);
|
|
858
|
+
if (!prUrl) {
|
|
859
|
+
jsonResponse(res, 400, { error: "Missing prUrl" });
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
const {
|
|
863
|
+
isPrRef,
|
|
864
|
+
parsePrRef,
|
|
865
|
+
resolveGitHubToken,
|
|
866
|
+
createGitHubClient,
|
|
867
|
+
fetchPullRequest,
|
|
868
|
+
fetchPullRequestDiff,
|
|
869
|
+
normalizePr
|
|
870
|
+
} = await import("./src-KF5HRJPX.js");
|
|
871
|
+
if (!isPrRef(prUrl)) {
|
|
872
|
+
jsonResponse(res, 400, {
|
|
873
|
+
error: "Invalid PR URL. Expected https://github.com/owner/repo/pull/123 or owner/repo#123"
|
|
874
|
+
});
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
let token;
|
|
878
|
+
try {
|
|
879
|
+
token = resolveGitHubToken();
|
|
880
|
+
} catch (err) {
|
|
881
|
+
jsonResponse(res, 401, {
|
|
882
|
+
error: err instanceof Error ? err.message : "GitHub token not found"
|
|
883
|
+
});
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
const { owner, repo, number: prNumber } = parsePrRef(prUrl);
|
|
887
|
+
const client = createGitHubClient(token);
|
|
888
|
+
const [prMetadata, rawDiff] = await Promise.all([
|
|
889
|
+
fetchPullRequest(client, owner, repo, prNumber),
|
|
890
|
+
fetchPullRequestDiff(client, owner, repo, prNumber)
|
|
891
|
+
]);
|
|
892
|
+
const normalized = normalizePr(rawDiff, prMetadata);
|
|
893
|
+
let localRepoPath = null;
|
|
894
|
+
try {
|
|
895
|
+
const { execSync } = await import("child_process");
|
|
896
|
+
const remoteOutput = execSync("git remote -v", {
|
|
897
|
+
cwd: process.cwd(),
|
|
898
|
+
encoding: "utf-8",
|
|
899
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
900
|
+
});
|
|
901
|
+
const repoPattern = new RegExp(`github\\.com[:/]${owner}/${repo}(\\.git)?\\s`, "i");
|
|
902
|
+
if (repoPattern.test(remoteOutput)) {
|
|
903
|
+
localRepoPath = process.cwd();
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
const sessionId = `session-${randomUUID2().slice(0, 8)}`;
|
|
908
|
+
normalized.payload.reviewId = sessionId;
|
|
909
|
+
const session = {
|
|
910
|
+
id: sessionId,
|
|
911
|
+
payload: normalized.payload,
|
|
912
|
+
projectPath: localRepoPath ?? `github:${owner}/${repo}#${prNumber}`,
|
|
913
|
+
source: "manual",
|
|
914
|
+
status: "pending",
|
|
915
|
+
createdAt: Date.now(),
|
|
916
|
+
result: null,
|
|
917
|
+
hasNewChanges: false,
|
|
918
|
+
annotations: []
|
|
919
|
+
};
|
|
920
|
+
sessions.set(sessionId, session);
|
|
921
|
+
broadcastToAll({
|
|
922
|
+
type: "session:added",
|
|
923
|
+
payload: toSummary(session)
|
|
924
|
+
});
|
|
925
|
+
jsonResponse(res, 201, {
|
|
926
|
+
sessionId,
|
|
927
|
+
fileCount: normalized.diffSet.files.length,
|
|
928
|
+
localRepoPath,
|
|
929
|
+
pr: {
|
|
930
|
+
title: prMetadata.title,
|
|
931
|
+
author: prMetadata.author,
|
|
932
|
+
url: prMetadata.url,
|
|
933
|
+
baseBranch: prMetadata.baseBranch,
|
|
934
|
+
headBranch: prMetadata.headBranch
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
} catch (err) {
|
|
938
|
+
jsonResponse(res, 500, {
|
|
939
|
+
error: err instanceof Error ? err.message : "Failed to fetch PR"
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
854
944
|
if (method === "GET" && req.url) {
|
|
855
945
|
const parsedUrl = new URL(req.url, "http://localhost");
|
|
856
946
|
if (parsedUrl.pathname === "/api/fs/list") {
|
|
@@ -903,6 +993,20 @@ async function handleApiRequest(req, res) {
|
|
|
903
993
|
jsonResponse(res, 200, toSummary(session));
|
|
904
994
|
return true;
|
|
905
995
|
}
|
|
996
|
+
const getPayloadParams = matchRoute(method, url, "GET", "/api/reviews/:id/payload");
|
|
997
|
+
if (getPayloadParams) {
|
|
998
|
+
const session = sessions.get(getPayloadParams.id);
|
|
999
|
+
if (!session) {
|
|
1000
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
jsonResponse(res, 200, {
|
|
1004
|
+
payload: session.payload,
|
|
1005
|
+
projectPath: session.projectPath,
|
|
1006
|
+
annotations: session.annotations
|
|
1007
|
+
});
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
906
1010
|
const postResultParams = matchRoute(method, url, "POST", "/api/reviews/:id/result");
|
|
907
1011
|
if (postResultParams) {
|
|
908
1012
|
const session = sessions.get(postResultParams.id);
|
|
@@ -1033,6 +1137,38 @@ async function handleApiRequest(req, res) {
|
|
|
1033
1137
|
jsonResponse(res, 200, { ok: true });
|
|
1034
1138
|
return true;
|
|
1035
1139
|
}
|
|
1140
|
+
const postFocusParams = matchRoute(method, url, "POST", "/api/reviews/:id/focus");
|
|
1141
|
+
if (postFocusParams) {
|
|
1142
|
+
const session = sessions.get(postFocusParams.id);
|
|
1143
|
+
if (!session) {
|
|
1144
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
try {
|
|
1148
|
+
const body = await readBody(req);
|
|
1149
|
+
const { file, lineStart, lineEnd } = JSON.parse(body);
|
|
1150
|
+
session.userFocus = {
|
|
1151
|
+
file,
|
|
1152
|
+
lineStart,
|
|
1153
|
+
lineEnd,
|
|
1154
|
+
updatedAt: Date.now()
|
|
1155
|
+
};
|
|
1156
|
+
jsonResponse(res, 200, { ok: true });
|
|
1157
|
+
} catch {
|
|
1158
|
+
jsonResponse(res, 400, { error: "Invalid request body" });
|
|
1159
|
+
}
|
|
1160
|
+
return true;
|
|
1161
|
+
}
|
|
1162
|
+
const getFocusParams = matchRoute(method, url, "GET", "/api/reviews/:id/focus");
|
|
1163
|
+
if (getFocusParams) {
|
|
1164
|
+
const session = sessions.get(getFocusParams.id);
|
|
1165
|
+
if (!session) {
|
|
1166
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1167
|
+
return true;
|
|
1168
|
+
}
|
|
1169
|
+
jsonResponse(res, 200, { focus: session.userFocus ?? null });
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1036
1172
|
const deleteParams = matchRoute(method, url, "DELETE", "/api/reviews/:id");
|
|
1037
1173
|
if (deleteParams) {
|
|
1038
1174
|
stopSessionWatcher(deleteParams.id);
|