diffprism 0.43.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 CHANGED
@@ -1,67 +1,130 @@
1
1
  # DiffPrism
2
2
 
3
- Your AI writes code you need to review it before it ships. But terminal diffs
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. Type `/review` in Claude Code your browser opens with the diff
14
- 2. Review the changes, leave inline comments, approve or request changes
15
- 3. Claude reads your decision and acts on it (commits, opens a PR, or fixes what you flagged)
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 for Claude Code
35
+ ## Setup
18
36
 
19
37
  ```bash
20
- npx diffprism setup
38
+ npm install -g diffprism
39
+ diffprism setup # Register MCP server with Claude Code
21
40
  ```
22
41
 
23
- Configures everything and opens a demo review so you can see it in action. Restart Claude Code afterward to load the MCP server.
42
+ Run the server from within your local clone so the AI gets full file context:
24
43
 
25
- ## Use from the CLI
44
+ ```bash
45
+ cd ~/dev/my-project
46
+ diffprism server # Or let it auto-start on first review
47
+ ```
26
48
 
27
- No setup needed — just run:
49
+ ## PR Review
28
50
 
29
51
  ```bash
30
- diffprism review # Review all changes (staged + unstaged)
31
- diffprism review --staged # Staged changes only
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
- ## Multi-Agent Reviews
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
- Running multiple Claude Code sessions (e.g., in git worktrees)? All reviews appear in one browser tab.
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
- The server starts automatically on first use — no manual setup needed. Each review shows up as a session with status badges, branch info, and change stats. Click to switch between reviews. Desktop notifications alert you when new reviews arrive.
92
+ ## Local Agent Review
93
+
94
+ DiffPrism also works for reviewing local agent-generated changes:
41
95
 
42
96
  ```bash
43
- diffprism server status # Check if server is running
44
- diffprism server stop # Stop the background server
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
- - **Syntax-highlighted diffs** — unified or split (side-by-side) view
50
- - **Inline commenting** — click any line to add `must_fix`, `suggestion`, `question`, or `nitpick` comments
51
- - **Review briefing** — complexity scores, test coverage gaps, pattern flags, dependency tracking
52
- - **Agent reasoning panel** — see why the AI made each change
53
- - **Quick actions** — Approve & Commit or Approve, Commit & PR from the review UI
54
- - **Multi-session dashboard** — review multiple agents from one browser tab
55
- - **Desktop notifications** — get alerted when a new review arrives
56
- - **GitHub PR review** — review any GitHub PR in DiffPrism's UI
57
- - **Keyboard shortcuts** — `j`/`k` files, `n`/`p` hunks, `c` comment, `s` status, `?` help
58
- - **Dark/light mode** — toggle with persistence
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
- ## Uninstall
118
+ ## CLI Reference
61
119
 
62
120
  ```bash
63
- npx diffprism teardown # Remove from current project
64
- npx diffprism teardown --global # Remove global config
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 (9 tools)
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
- normalizePr,
8
- parsePrRef,
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-WDOB26I4.js";
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-VR2C6WTQ.js";
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 { result } = await submitReviewToServer(serverInfo, `PR #${number}`, {
87
- injectedPayload: payload,
88
- projectPath: `github:${owner}/${repo}`,
89
- diffRef: `PR #${number}`
90
- });
91
- console.log(JSON.stringify(result, null, 2));
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
- process.exit(0);
100
- }
101
- async function promptPostToGithub() {
102
- if (!process.stdin.isTTY) {
103
- return false;
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
- const rl = readline.createInterface({
106
- input: process.stdin,
107
- output: process.stdout
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 readline2 from "readline";
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 9 DiffPrism MCP tools available. Use them proactively \u2014 don't wait for the user to ask.
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
- To review a GitHub pull request:
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
- 1. Call \`mcp__diffprism__review_pr\` with \`pr: "owner/repo#123"\` or a full GitHub PR URL
181
- 2. Set \`post_to_github: true\` to post the review back to GitHub after the human submits
182
- 3. The tool fetches the PR diff, runs analysis, and opens the review UI
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. Blocks until submitted. |
190
- | \`review_pr\` | Open browser review UI for a GitHub PR. Blocks until submitted. |
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
- ### Annotation & Flagging
180
+ ### PR Super Review
201
181
  | Tool | Purpose |
202
182
  |------|---------|
203
- | \`add_annotation\` | Post a finding/suggestion/question on a specific line in a running review. |
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 = readline2.createInterface({
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-EMHF7QFK.js");
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.43.0" : "0.0.0-dev";
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.43.0" : "0.0.0-dev");
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);
@@ -668,6 +668,16 @@ function recordReviewHistory(session, result) {
668
668
  } catch {
669
669
  }
670
670
  }
671
+ function isInsideGitRepo(dirPath) {
672
+ let current = dirPath;
673
+ while (current !== "/") {
674
+ if (fs5.existsSync(`${current}/.git`)) return true;
675
+ const parent = current.replace(/\/[^/]+$/, "") || "/";
676
+ if (parent === current) break;
677
+ current = parent;
678
+ }
679
+ return fs5.existsSync(`${current}/.git`);
680
+ }
671
681
  async function handleApiRequest(req, res) {
672
682
  const method = req.method ?? "GET";
673
683
  const url = (req.url ?? "/").split("?")[0];
@@ -841,6 +851,96 @@ async function handleApiRequest(req, res) {
841
851
  }
842
852
  return true;
843
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
+ }
844
944
  if (method === "GET" && req.url) {
845
945
  const parsedUrl = new URL(req.url, "http://localhost");
846
946
  if (parsedUrl.pathname === "/api/fs/list") {
@@ -869,7 +969,7 @@ async function handleApiRequest(req, res) {
869
969
  if (a.isGitRepo !== b.isGitRepo) return a.isGitRepo ? -1 : 1;
870
970
  return a.name.localeCompare(b.name);
871
971
  });
872
- const isGitRepo = fs5.existsSync(`${dirPath}/.git`);
972
+ const isGitRepo = isInsideGitRepo(dirPath);
873
973
  const parentPath = dirPath === "/" ? null : dirPath.replace(/\/[^/]+$/, "") || "/";
874
974
  jsonResponse(res, 200, { path: dirPath, parentPath, isGitRepo, dirs });
875
975
  } catch {
@@ -893,6 +993,20 @@ async function handleApiRequest(req, res) {
893
993
  jsonResponse(res, 200, toSummary(session));
894
994
  return true;
895
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
+ }
896
1010
  const postResultParams = matchRoute(method, url, "POST", "/api/reviews/:id/result");
897
1011
  if (postResultParams) {
898
1012
  const session = sessions.get(postResultParams.id);
@@ -1023,6 +1137,38 @@ async function handleApiRequest(req, res) {
1023
1137
  jsonResponse(res, 200, { ok: true });
1024
1138
  return true;
1025
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
+ }
1026
1172
  const deleteParams = matchRoute(method, url, "DELETE", "/api/reviews/:id");
1027
1173
  if (deleteParams) {
1028
1174
  stopSessionWatcher(deleteParams.id);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ensureServer,
3
3
  submitReviewToServer
4
- } from "./chunk-VR2C6WTQ.js";
4
+ } from "./chunk-RICVPPTE.js";
5
5
  import {
6
6
  parseDiff
7
7
  } from "./chunk-QGWYCEJN.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  demo
3
- } from "./chunk-WDOB26I4.js";
4
- import "./chunk-VR2C6WTQ.js";
3
+ } from "./chunk-RVI74JF5.js";
4
+ import "./chunk-RICVPPTE.js";
5
5
  import "./chunk-QGWYCEJN.js";
6
6
  import "./chunk-DHCVZGHE.js";
7
7
  import "./chunk-JSBRDJBE.js";