diffprism 0.34.1 → 0.36.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 +47 -188
- package/dist/bin.js +213 -227
- package/dist/chunk-DHCVZGHE.js +501 -0
- package/dist/chunk-ITPHDFOS.js +1283 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/{chunk-VASCXEMN.js → chunk-OR6PCPZX.js} +22 -2791
- package/dist/chunk-QGWYCEJN.js +448 -0
- package/dist/chunk-UYZ3A2PB.js +231 -0
- package/dist/demo-JH5YOKTZ.js +10 -0
- package/dist/mcp-server.js +77 -280
- package/dist/src-AMCPIYDZ.js +19 -0
- package/dist/src-JMPTSU3P.js +27 -0
- package/package.json +1 -1
- package/ui-dist/assets/index-CNIXSkOg.js +325 -0
- package/ui-dist/index.html +1 -1
- package/ui-dist/assets/index-BfqEajZq.js +0 -325
package/dist/bin.js
CHANGED
|
@@ -3,17 +3,24 @@ import {
|
|
|
3
3
|
createGitHubClient,
|
|
4
4
|
fetchPullRequest,
|
|
5
5
|
fetchPullRequestDiff,
|
|
6
|
-
isServerAlive,
|
|
7
6
|
normalizePr,
|
|
8
7
|
parsePrRef,
|
|
9
|
-
readServerFile,
|
|
10
|
-
readWatchFile,
|
|
11
8
|
resolveGitHubToken,
|
|
12
|
-
startGlobalServer,
|
|
13
|
-
startReview,
|
|
14
|
-
startWatch,
|
|
15
9
|
submitGitHubReview
|
|
16
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OR6PCPZX.js";
|
|
11
|
+
import {
|
|
12
|
+
demo
|
|
13
|
+
} from "./chunk-UYZ3A2PB.js";
|
|
14
|
+
import {
|
|
15
|
+
ensureServer,
|
|
16
|
+
isServerAlive,
|
|
17
|
+
readServerFile,
|
|
18
|
+
startGlobalServer,
|
|
19
|
+
submitReviewToServer
|
|
20
|
+
} from "./chunk-ITPHDFOS.js";
|
|
21
|
+
import "./chunk-QGWYCEJN.js";
|
|
22
|
+
import "./chunk-DHCVZGHE.js";
|
|
23
|
+
import "./chunk-JSBRDJBE.js";
|
|
17
24
|
|
|
18
25
|
// cli/src/index.ts
|
|
19
26
|
import { Command } from "commander";
|
|
@@ -31,11 +38,12 @@ async function review(ref, flags) {
|
|
|
31
38
|
diffRef = "working-copy";
|
|
32
39
|
}
|
|
33
40
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
41
|
+
const serverInfo = await ensureServer({ dev: flags.dev });
|
|
42
|
+
console.log("Opening review in browser...");
|
|
43
|
+
const { result } = await submitReviewToServer(serverInfo, diffRef, {
|
|
36
44
|
title: flags.title,
|
|
37
45
|
cwd: process.cwd(),
|
|
38
|
-
|
|
46
|
+
diffRef
|
|
39
47
|
});
|
|
40
48
|
console.log(JSON.stringify(result, null, 2));
|
|
41
49
|
process.exit(0);
|
|
@@ -62,46 +70,19 @@ async function reviewPr(pr, flags) {
|
|
|
62
70
|
console.log("PR has no changes to review.");
|
|
63
71
|
return;
|
|
64
72
|
}
|
|
65
|
-
const { payload, diffSet
|
|
73
|
+
const { payload, diffSet } = normalizePr(rawDiff, prMetadata, {
|
|
66
74
|
title: flags.title,
|
|
67
75
|
reasoning: flags.reasoning
|
|
68
76
|
});
|
|
69
77
|
console.log(
|
|
70
78
|
`${diffSet.files.length} files, +${diffSet.files.reduce((s, f) => s + f.additions, 0)} -${diffSet.files.reduce((s, f) => s + f.deletions, 0)}`
|
|
71
79
|
);
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}
|
|
80
|
+
const serverInfo = await ensureServer({ dev: flags.dev });
|
|
81
|
+
const { result } = await submitReviewToServer(serverInfo, `PR #${number}`, {
|
|
82
|
+
injectedPayload: payload,
|
|
83
|
+
projectPath: `github:${owner}/${repo}`,
|
|
84
|
+
diffRef: `PR #${number}`
|
|
85
|
+
});
|
|
105
86
|
console.log(JSON.stringify(result, null, 2));
|
|
106
87
|
if (flags.postToGithub || result.decision !== "dismissed" && await promptPostToGithub()) {
|
|
107
88
|
console.log("Posting review to GitHub...");
|
|
@@ -117,24 +98,6 @@ async function reviewPr(pr, flags) {
|
|
|
117
98
|
process.exit(1);
|
|
118
99
|
}
|
|
119
100
|
}
|
|
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
101
|
async function promptPostToGithub() {
|
|
139
102
|
if (!process.stdin.isTTY) {
|
|
140
103
|
return false;
|
|
@@ -169,130 +132,30 @@ name: review
|
|
|
169
132
|
description: Open current code changes in DiffPrism's browser-based review UI for human review.
|
|
170
133
|
---
|
|
171
134
|
|
|
172
|
-
# DiffPrism Review
|
|
173
|
-
|
|
174
|
-
When the user invokes \`/review\`, open the current code changes in DiffPrism for browser-based human review.
|
|
175
|
-
|
|
176
|
-
## Steps
|
|
177
|
-
|
|
178
|
-
### 1. Check for Watch Mode
|
|
179
|
-
|
|
180
|
-
Before opening a new review, check if \`diffprism watch\` is already running. Look for \`.diffprism/watch.json\` at the git root. If it exists and the process is alive:
|
|
181
|
-
|
|
182
|
-
- **Do NOT call \`open_review\`** (the browser is already open with live-updating diffs)
|
|
183
|
-
- Instead, call \`mcp__diffprism__update_review_context\` to push your reasoning to the existing watch session
|
|
184
|
-
- Then **immediately** call \`mcp__diffprism__get_review_result\` with \`wait: true\` to block until the developer submits their review
|
|
185
|
-
- Tell the user: "DiffPrism watch is running \u2014 pushed reasoning to the live review. Waiting for your feedback..."
|
|
186
|
-
- When the result comes back, handle it per step 5 below
|
|
187
|
-
- Skip steps 2-4
|
|
188
|
-
|
|
189
|
-
### 2. Load Configuration
|
|
190
|
-
|
|
191
|
-
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.
|
|
192
|
-
|
|
193
|
-
\`\`\`json
|
|
194
|
-
{
|
|
195
|
-
"defaultDiffScope": "staged | unstaged | working-copy",
|
|
196
|
-
"includeReasoning": true | false
|
|
197
|
-
}
|
|
198
|
-
\`\`\`
|
|
199
|
-
|
|
200
|
-
**Defaults** (when fields are missing or file doesn't exist):
|
|
201
|
-
- \`defaultDiffScope\`: \`"working-copy"\`
|
|
202
|
-
- \`includeReasoning\`: \`true\`
|
|
203
|
-
|
|
204
|
-
### 3. Open the Review
|
|
205
|
-
|
|
206
|
-
Call \`mcp__diffprism__open_review\` with:
|
|
207
|
-
|
|
208
|
-
- \`diff_ref\`: Use the \`defaultDiffScope\` from config. If the user specified a scope in their message (e.g., "/review staged"), use that instead.
|
|
209
|
-
- \`title\`: A short summary of the changes (generate from git status or the user's message).
|
|
210
|
-
- \`description\`: A brief description of what changed and why.
|
|
211
|
-
- \`reasoning\`: If \`includeReasoning\` is \`true\`, include your reasoning about the implementation decisions.
|
|
212
|
-
|
|
213
|
-
### 4. Handle the Result
|
|
135
|
+
# DiffPrism Review
|
|
214
136
|
|
|
215
|
-
|
|
137
|
+
When the user invokes \`/review\`, call \`mcp__diffprism__open_review\` with:
|
|
216
138
|
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
-
|
|
139
|
+
- \`diff_ref\`: \`"working-copy"\` (or what the user specified, e.g. \`"staged"\`)
|
|
140
|
+
- \`title\`: Brief summary of the changes
|
|
141
|
+
- \`reasoning\`: Your reasoning about the implementation decisions
|
|
220
142
|
|
|
221
|
-
|
|
143
|
+
The tool blocks until the human submits their review. Handle the result:
|
|
222
144
|
|
|
223
|
-
|
|
145
|
+
- **\`approved\`** \u2014 Proceed with the task.
|
|
146
|
+
- **\`changes_requested\`** \u2014 Read comments, make fixes, offer to re-review.
|
|
147
|
+
- If \`postReviewAction\` is \`"commit"\` \u2014 commit the changes.
|
|
148
|
+
- If \`postReviewAction\` is \`"commit_and_pr"\` \u2014 commit and open a PR.
|
|
224
149
|
|
|
225
|
-
|
|
226
|
-
- **\`"commit_and_pr"\`** \u2014 Commit the changes and open a pull request.
|
|
150
|
+
## Headless Tools
|
|
227
151
|
|
|
228
|
-
|
|
152
|
+
- \`mcp__diffprism__analyze_diff\` \u2014 Returns analysis JSON (patterns, complexity, test gaps) without opening a browser. Use proactively to self-check before requesting review.
|
|
153
|
+
- \`mcp__diffprism__get_diff\` \u2014 Returns structured diff JSON.
|
|
229
154
|
|
|
230
|
-
|
|
155
|
+
## Rules
|
|
231
156
|
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
## Global Server Mode
|
|
236
|
-
|
|
237
|
-
When a global DiffPrism server is running (\`diffprism server\`), the MCP tools automatically detect it and route reviews there instead of opening a new browser tab each time. The review appears in the server's multi-session UI at the existing browser tab.
|
|
238
|
-
|
|
239
|
-
This is transparent \u2014 the same \`open_review\`, \`update_review_context\`, and \`get_review_result\` tools work the same way. No changes to the workflow are needed.
|
|
240
|
-
|
|
241
|
-
## Watch Mode: Waiting for Review Feedback
|
|
242
|
-
|
|
243
|
-
When \`diffprism watch\` is active (detected via \`.diffprism/watch.json\`), the developer can submit reviews at any time in the browser.
|
|
244
|
-
|
|
245
|
-
**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).
|
|
246
|
-
|
|
247
|
-
Use this pattern:
|
|
248
|
-
1. Push context via \`update_review_context\`
|
|
249
|
-
2. Call \`get_review_result\` with \`wait: true\` \u2014 this blocks until the developer submits
|
|
250
|
-
3. Handle the result (approved, changes_requested, etc.)
|
|
251
|
-
4. If changes were requested, make fixes, push updated context, and call \`get_review_result\` with \`wait: true\` again
|
|
252
|
-
|
|
253
|
-
You can also check for feedback without blocking by calling \`get_review_result\` without \`wait\` at natural breakpoints (between tasks, before committing, etc.).
|
|
254
|
-
|
|
255
|
-
## Self-Review: Headless Analysis Tools
|
|
256
|
-
|
|
257
|
-
DiffPrism provides two headless tools that return analysis data as JSON without opening a browser. Use these to check your own work before requesting human review.
|
|
258
|
-
|
|
259
|
-
### Available Headless Tools
|
|
260
|
-
|
|
261
|
-
- **\`mcp__diffprism__get_diff\`** \u2014 Returns a structured \`DiffSet\` (files, hunks, additions/deletions) for a given diff ref. Use this to inspect exactly what changed.
|
|
262
|
-
- **\`mcp__diffprism__analyze_diff\`** \u2014 Returns a \`ReviewBriefing\` with summary, file triage, impact detection, complexity scores, test coverage gaps, and pattern flags (security issues, TODOs, console.logs left in, etc.).
|
|
263
|
-
|
|
264
|
-
Both accept a \`diff_ref\` parameter: \`"staged"\`, \`"unstaged"\`, \`"working-copy"\`, or a git range like \`"HEAD~3..HEAD"\`.
|
|
265
|
-
|
|
266
|
-
### Self-Review Loop
|
|
267
|
-
|
|
268
|
-
When you've finished writing code and before requesting human review, use this pattern:
|
|
269
|
-
|
|
270
|
-
1. **Analyze your changes:** Call \`mcp__diffprism__analyze_diff\` with \`diff_ref: "working-copy"\`
|
|
271
|
-
2. **Check the briefing for issues:**
|
|
272
|
-
- \`patterns\` \u2014 Look for console.logs, TODOs, security flags, disabled tests
|
|
273
|
-
- \`testCoverage\` \u2014 Check if changed source files have corresponding test changes
|
|
274
|
-
- \`complexity\` \u2014 Review high-complexity scores
|
|
275
|
-
- \`impact.newDependencies\` \u2014 Verify any new deps are intentional
|
|
276
|
-
- \`impact.breakingChanges\` \u2014 Confirm breaking changes are expected
|
|
277
|
-
3. **Fix any issues found** \u2014 Remove debug statements, add missing tests, address security flags
|
|
278
|
-
4. **Re-analyze** \u2014 Run \`analyze_diff\` again to confirm the issues are resolved
|
|
279
|
-
5. **Open for human review** \u2014 Once clean, use \`/review\` or \`open_review\` for final human sign-off
|
|
280
|
-
|
|
281
|
-
This loop catches common issues (leftover console.logs, missing tests, security anti-patterns) before the human reviewer sees them, making reviews faster and more focused.
|
|
282
|
-
|
|
283
|
-
### When to Use Headless Tools
|
|
284
|
-
|
|
285
|
-
- **After completing a coding task** \u2014 Self-check before requesting review
|
|
286
|
-
- **During implementation** \u2014 Periodically check for patterns and issues as you work
|
|
287
|
-
- **Before committing** \u2014 Quick sanity check on what's about to be committed
|
|
288
|
-
- **Do NOT use these as a replacement for human review** \u2014 They complement, not replace, \`/review\`
|
|
289
|
-
|
|
290
|
-
## Behavior Rules
|
|
291
|
-
|
|
292
|
-
- **IMPORTANT: Do NOT open reviews automatically.** Only open a review when the user explicitly invokes \`/review\` or directly asks for a review.
|
|
293
|
-
- Do NOT open reviews before commits, after code changes, or at any other time unless the user requests it.
|
|
294
|
-
- Headless tools (\`get_diff\`, \`analyze_diff\`) can be used proactively during development without explicit user request \u2014 they don't open a browser or interrupt the user.
|
|
295
|
-
- Power users can create \`diffprism.config.json\` manually to customize defaults (diff scope, reasoning).
|
|
157
|
+
- Only open a review when the user explicitly asks (\`/review\` or "review my changes").
|
|
158
|
+
- Headless tools can be used proactively without user request.
|
|
296
159
|
`;
|
|
297
160
|
|
|
298
161
|
// cli/src/commands/setup.ts
|
|
@@ -494,7 +357,84 @@ async function setupGitignore(gitRoot) {
|
|
|
494
357
|
fs.writeFileSync(filePath, GITIGNORE_ENTRIES.map((e) => e + "\n").join(""));
|
|
495
358
|
return { action: "created", filePath };
|
|
496
359
|
}
|
|
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
|
+
}
|
|
497
380
|
async function setup(flags) {
|
|
381
|
+
const force = flags.force ?? false;
|
|
382
|
+
const global = flags.global ?? false;
|
|
383
|
+
const quiet = flags.quiet ?? false;
|
|
384
|
+
const isInteractive = !global && !force && !quiet && process.stdin.isTTY;
|
|
385
|
+
if (isInteractive) {
|
|
386
|
+
return setupInteractive(flags);
|
|
387
|
+
}
|
|
388
|
+
return setupBatch(flags);
|
|
389
|
+
}
|
|
390
|
+
async function setupInteractive(flags) {
|
|
391
|
+
const dev = flags.dev;
|
|
392
|
+
const gitRoot = findGitRoot(process.cwd());
|
|
393
|
+
console.log("\n Welcome to DiffPrism");
|
|
394
|
+
console.log(" Browser-based code review for agent-generated changes.\n");
|
|
395
|
+
const modeChoice = await promptChoice("\nHow will you use DiffPrism? ", [
|
|
396
|
+
"With Claude Code (recommended)",
|
|
397
|
+
"From the CLI only"
|
|
398
|
+
]);
|
|
399
|
+
if (modeChoice === 0) {
|
|
400
|
+
if (!gitRoot) {
|
|
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 });
|
|
412
|
+
if (outcome.created.length > 0 || outcome.updated.length > 0) {
|
|
413
|
+
console.log(" \u2713 Registered MCP server (.mcp.json)");
|
|
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");
|
|
417
|
+
} else {
|
|
418
|
+
console.log(" \u2713 Already configured");
|
|
419
|
+
}
|
|
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
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function setupBatch(flags) {
|
|
498
438
|
const force = flags.force ?? false;
|
|
499
439
|
const global = flags.global ?? false;
|
|
500
440
|
const quiet = flags.quiet ?? false;
|
|
@@ -808,7 +748,7 @@ async function start(ref, flags) {
|
|
|
808
748
|
});
|
|
809
749
|
const hasChanges = outcome.created.length > 0 || outcome.updated.length > 0;
|
|
810
750
|
if (hasChanges) {
|
|
811
|
-
console.log("
|
|
751
|
+
console.log("DiffPrism configured for Claude Code.");
|
|
812
752
|
}
|
|
813
753
|
let diffRef;
|
|
814
754
|
if (flags.staged) {
|
|
@@ -820,30 +760,23 @@ async function start(ref, flags) {
|
|
|
820
760
|
} else {
|
|
821
761
|
diffRef = "working-copy";
|
|
822
762
|
}
|
|
823
|
-
const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
|
|
824
763
|
try {
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
dev: flags.dev,
|
|
830
|
-
pollInterval
|
|
831
|
-
});
|
|
832
|
-
console.log("Use /review in Claude Code to send changes for review.");
|
|
764
|
+
const serverInfo = await ensureServer({ dev: flags.dev });
|
|
765
|
+
console.log(
|
|
766
|
+
`DiffPrism server at http://localhost:${serverInfo.httpPort}`
|
|
767
|
+
);
|
|
833
768
|
if (hasChanges) {
|
|
834
769
|
console.log(
|
|
835
770
|
"If this is your first time, restart Claude Code first to load the MCP server."
|
|
836
771
|
);
|
|
837
772
|
}
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
};
|
|
843
|
-
process.on("SIGINT", shutdown);
|
|
844
|
-
process.on("SIGTERM", shutdown);
|
|
845
|
-
await new Promise(() => {
|
|
773
|
+
const { result } = await submitReviewToServer(serverInfo, diffRef, {
|
|
774
|
+
title: flags.title,
|
|
775
|
+
cwd: process.cwd(),
|
|
776
|
+
diffRef
|
|
846
777
|
});
|
|
778
|
+
console.log(JSON.stringify(result, null, 2));
|
|
779
|
+
process.exit(0);
|
|
847
780
|
} catch (err) {
|
|
848
781
|
const message = err instanceof Error ? err.message : String(err);
|
|
849
782
|
console.error(`Error: ${message}`);
|
|
@@ -863,24 +796,19 @@ async function watch(ref, flags) {
|
|
|
863
796
|
} else {
|
|
864
797
|
diffRef = "working-copy";
|
|
865
798
|
}
|
|
866
|
-
const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
|
|
867
799
|
try {
|
|
868
|
-
const
|
|
869
|
-
|
|
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, {
|
|
870
806
|
title: flags.title,
|
|
871
807
|
cwd: process.cwd(),
|
|
872
|
-
|
|
873
|
-
pollInterval
|
|
874
|
-
});
|
|
875
|
-
const shutdown = async () => {
|
|
876
|
-
console.log("\nStopping watch...");
|
|
877
|
-
await handle.stop();
|
|
878
|
-
process.exit(0);
|
|
879
|
-
};
|
|
880
|
-
process.on("SIGINT", shutdown);
|
|
881
|
-
process.on("SIGTERM", shutdown);
|
|
882
|
-
await new Promise(() => {
|
|
808
|
+
diffRef
|
|
883
809
|
});
|
|
810
|
+
console.log(JSON.stringify(result, null, 2));
|
|
811
|
+
process.exit(0);
|
|
884
812
|
} catch (err) {
|
|
885
813
|
const message = err instanceof Error ? err.message : String(err);
|
|
886
814
|
console.error(`Error: ${message}`);
|
|
@@ -891,15 +819,15 @@ async function watch(ref, flags) {
|
|
|
891
819
|
// cli/src/commands/notify-stop.ts
|
|
892
820
|
async function notifyStop() {
|
|
893
821
|
try {
|
|
894
|
-
const
|
|
895
|
-
if (!
|
|
822
|
+
const serverInfo = await isServerAlive();
|
|
823
|
+
if (!serverInfo) {
|
|
896
824
|
process.exit(0);
|
|
897
825
|
return;
|
|
898
826
|
}
|
|
899
827
|
const controller = new AbortController();
|
|
900
828
|
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
901
829
|
try {
|
|
902
|
-
await fetch(`http://localhost:${
|
|
830
|
+
await fetch(`http://localhost:${serverInfo.httpPort}/api/refresh`, {
|
|
903
831
|
method: "POST",
|
|
904
832
|
signal: controller.signal
|
|
905
833
|
});
|
|
@@ -912,18 +840,33 @@ async function notifyStop() {
|
|
|
912
840
|
}
|
|
913
841
|
|
|
914
842
|
// cli/src/commands/server.ts
|
|
843
|
+
import { spawn } from "child_process";
|
|
844
|
+
import fs3 from "fs";
|
|
845
|
+
import path3 from "path";
|
|
846
|
+
import os3 from "os";
|
|
915
847
|
async function server(flags) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
|
|
919
|
-
console.log(`Use 'diffprism server stop' to stop it first.`);
|
|
920
|
-
process.exit(1);
|
|
848
|
+
if (flags.background) {
|
|
849
|
+
await spawnDaemon(flags);
|
|
921
850
|
return;
|
|
922
851
|
}
|
|
852
|
+
const isDaemon = !!flags._daemon;
|
|
853
|
+
if (!isDaemon) {
|
|
854
|
+
const existing = await isServerAlive();
|
|
855
|
+
if (existing) {
|
|
856
|
+
console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
|
|
857
|
+
console.log(`Use 'diffprism server stop' to stop it first.`);
|
|
858
|
+
process.exit(1);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
923
862
|
if (!isGlobalSetupDone()) {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
863
|
+
if (!isDaemon) {
|
|
864
|
+
console.log("Running global setup...\n");
|
|
865
|
+
}
|
|
866
|
+
await setup({ global: true, quiet: isDaemon });
|
|
867
|
+
if (!isDaemon) {
|
|
868
|
+
console.log("");
|
|
869
|
+
}
|
|
927
870
|
}
|
|
928
871
|
const httpPort = flags.port ? parseInt(flags.port, 10) : void 0;
|
|
929
872
|
const wsPort = flags.wsPort ? parseInt(flags.wsPort, 10) : void 0;
|
|
@@ -931,10 +874,14 @@ async function server(flags) {
|
|
|
931
874
|
const handle = await startGlobalServer({
|
|
932
875
|
httpPort,
|
|
933
876
|
wsPort,
|
|
934
|
-
dev: flags.dev
|
|
877
|
+
dev: flags.dev,
|
|
878
|
+
silent: isDaemon,
|
|
879
|
+
openBrowser: !isDaemon
|
|
935
880
|
});
|
|
936
881
|
const shutdown = async () => {
|
|
937
|
-
|
|
882
|
+
if (!isDaemon) {
|
|
883
|
+
console.log("\nStopping server...");
|
|
884
|
+
}
|
|
938
885
|
await handle.stop();
|
|
939
886
|
process.exit(0);
|
|
940
887
|
};
|
|
@@ -944,10 +891,48 @@ async function server(flags) {
|
|
|
944
891
|
});
|
|
945
892
|
} catch (err) {
|
|
946
893
|
const message = err instanceof Error ? err.message : String(err);
|
|
947
|
-
|
|
894
|
+
if (!isDaemon) {
|
|
895
|
+
console.error(`Error starting server: ${message}`);
|
|
896
|
+
}
|
|
948
897
|
process.exit(1);
|
|
949
898
|
}
|
|
950
899
|
}
|
|
900
|
+
async function spawnDaemon(flags) {
|
|
901
|
+
const existing = await isServerAlive();
|
|
902
|
+
if (existing) {
|
|
903
|
+
console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const args = process.argv.slice(1).filter((a) => a !== "--background");
|
|
907
|
+
args.push("--_daemon");
|
|
908
|
+
const logDir = path3.join(os3.homedir(), ".diffprism");
|
|
909
|
+
if (!fs3.existsSync(logDir)) {
|
|
910
|
+
fs3.mkdirSync(logDir, { recursive: true });
|
|
911
|
+
}
|
|
912
|
+
const logPath = path3.join(logDir, "server.log");
|
|
913
|
+
const logFd = fs3.openSync(logPath, "a");
|
|
914
|
+
const child = spawn(process.execPath, args, {
|
|
915
|
+
detached: true,
|
|
916
|
+
stdio: ["ignore", logFd, logFd],
|
|
917
|
+
env: { ...process.env }
|
|
918
|
+
});
|
|
919
|
+
child.unref();
|
|
920
|
+
fs3.closeSync(logFd);
|
|
921
|
+
console.log("Starting DiffPrism server in background...");
|
|
922
|
+
const startTime = Date.now();
|
|
923
|
+
const timeoutMs = 15e3;
|
|
924
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
925
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
926
|
+
const info = await isServerAlive();
|
|
927
|
+
if (info) {
|
|
928
|
+
console.log(`DiffPrism server started (PID ${info.pid}, port ${info.httpPort})`);
|
|
929
|
+
console.log(`Logs: ${logPath}`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
console.error("Timed out waiting for server to start. Check logs at:", logPath);
|
|
934
|
+
process.exit(1);
|
|
935
|
+
}
|
|
951
936
|
async function serverStatus() {
|
|
952
937
|
const info = await isServerAlive();
|
|
953
938
|
if (!info) {
|
|
@@ -1001,20 +986,21 @@ async function serverStop() {
|
|
|
1001
986
|
|
|
1002
987
|
// cli/src/index.ts
|
|
1003
988
|
var program = new Command();
|
|
1004
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
989
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.36.0" : "0.0.0-dev");
|
|
990
|
+
program.command("demo").description("Open a sample review to see DiffPrism in action").option("--dev", "Use Vite dev server").action(demo);
|
|
1005
991
|
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
992
|
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);
|
|
1007
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);
|
|
1008
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);
|
|
1009
995
|
program.command("notify-stop").description("Signal the watch server to refresh (used by Claude Code hooks)").action(notifyStop);
|
|
1010
996
|
program.command("serve").description("Start the MCP server for Claude Code integration").action(serve);
|
|
1011
|
-
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").action((flags) => {
|
|
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) => {
|
|
1012
998
|
setup(flags);
|
|
1013
999
|
});
|
|
1014
1000
|
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) => {
|
|
1015
1001
|
teardown(flags);
|
|
1016
1002
|
});
|
|
1017
|
-
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);
|
|
1003
|
+
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").option("--background", "Start server as a background daemon").option("--_daemon", "Internal: run as spawned daemon (do not use directly)").action(server);
|
|
1018
1004
|
serverCmd.command("status").description("Check if the global server is running and list active sessions").action(serverStatus);
|
|
1019
1005
|
serverCmd.command("stop").description("Stop the running global server").action(serverStop);
|
|
1020
1006
|
program.parse();
|