diffprism 1.3.2 → 1.4.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
@@ -89,7 +89,7 @@ Reviews are **one per repo**: opening a review for a repo that already has one u
89
89
 
90
90
  In a local review, **Ask agent now** in a line's comment form asks the agent while you're still reviewing: the agent's wait for your decision ends with your question, it answers in the thread, and goes back to waiting.
91
91
 
92
- PR reviews are opened with `diffprism review <PR URL>` or the dashboard — not by `open_review` — and your AI then works inside them with the tools above. Click a line to ask the agent about it; an agent listening with `wait_for_comments` answers in the thread.
92
+ PR reviews are opened with `diffprism review <PR URL>` or the dashboard — not by `open_review` — and your AI then works inside them with the tools above. Click a line to ask the agent about it; an agent listening with `wait_for_comments` answers in the thread. Nothing starts an agent for you: if none has picked a question up, the thread says so and names the session to ask Claude Code about.
93
93
 
94
94
  When you're done, **Approve**, **Request changes** or **Comment** from the bar at the bottom: DiffPrism posts it to GitHub as a pull request review. Your threads are a conversation with the agent, so none of them goes to GitHub unless you tick it; a ticked thread posts your opening message as an inline review comment. The token comes from `GITHUB_TOKEN`, `gh auth token`, or `~/.diffprism/config.json`.
95
95
 
package/dist/bin.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  isPrRef,
4
4
  parsePrRef
5
- } from "./chunk-EPU4F7WT.js";
5
+ } from "./chunk-RFDSC6MC.js";
6
6
  import {
7
7
  demo
8
- } from "./chunk-XGQRRG2H.js";
8
+ } from "./chunk-6P6QJPWT.js";
9
9
  import {
10
10
  COMMIT_GATE_DIFF_REF,
11
11
  DEFAULT_DIFF_REF,
@@ -26,10 +26,10 @@ import {
26
26
  recordError,
27
27
  startGlobalServer,
28
28
  submitReviewToServer
29
- } from "./chunk-USOWTWJD.js";
29
+ } from "./chunk-SCQFIDIK.js";
30
30
  import {
31
31
  getDiff
32
- } from "./chunk-3GMPE2ZR.js";
32
+ } from "./chunk-GGK6TIQZ.js";
33
33
  import "./chunk-DHCVZGHE.js";
34
34
  import "./chunk-JSBRDJBE.js";
35
35
 
@@ -518,6 +518,8 @@ The reviewer can also ask you questions on lines of the PR. Hold that conversati
518
518
  2. \`mcp__diffprism__reply\` \u2014 answer each thread, passing its \`annotation_id\`.
519
519
  3. Wait again. On \`timed_out\`, nothing new was said \u2014 keep waiting until the user tells you to stop.
520
520
 
521
+ When no agent has read a question, the dashboard tells the reviewer so, and to ask you to answer their DiffPrism comments on a session id. When they do, run this loop with that \`session_id\`.
522
+
521
523
  A PR review and a working-copy review can be open for the same clone at once. If a tool reports more than one session, pass the \`session_id\` of the one you mean.
522
524
 
523
525
  ## Tool Reference
@@ -719,7 +721,7 @@ async function setupInteractive(flags) {
719
721
  }
720
722
  async function runDemo(dev) {
721
723
  console.log("");
722
- const { demo: demo2 } = await import("./demo-M3O63OIX.js");
724
+ const { demo: demo2 } = await import("./demo-GHOE7YNU.js");
723
725
  await demo2({ dev });
724
726
  }
725
727
  async function setupBatch(flags) {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ensureServer,
3
3
  submitReviewToServer
4
- } from "./chunk-USOWTWJD.js";
4
+ } from "./chunk-SCQFIDIK.js";
5
5
  import {
6
6
  parseDiff
7
- } from "./chunk-3GMPE2ZR.js";
7
+ } from "./chunk-GGK6TIQZ.js";
8
8
  import {
9
9
  analyze
10
10
  } from "./chunk-DHCVZGHE.js";
@@ -210,6 +210,27 @@ function getRepoRoot(options) {
210
210
  return null;
211
211
  }
212
212
  }
213
+ function getGitHubRemotes(options) {
214
+ const cwd = options?.cwd ?? process.cwd();
215
+ let output;
216
+ try {
217
+ output = execSync("git remote -v", {
218
+ cwd,
219
+ encoding: "utf-8",
220
+ stdio: ["pipe", "pipe", "pipe"]
221
+ });
222
+ } catch {
223
+ return [];
224
+ }
225
+ const repos = /* @__PURE__ */ new Set();
226
+ for (const line of output.split("\n")) {
227
+ const match = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\s/i.exec(line);
228
+ if (match) {
229
+ repos.add(`${match[1]}/${match[2]}`.toLowerCase());
230
+ }
231
+ }
232
+ return Array.from(repos);
233
+ }
213
234
 
214
235
  // packages/git/src/parser.ts
215
236
  import path2 from "path";
@@ -456,6 +477,7 @@ export {
456
477
  listCommits,
457
478
  detectWorktree,
458
479
  getRepoRoot,
480
+ getGitHubRemotes,
459
481
  parseDiff,
460
482
  getDiff
461
483
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseDiff
3
- } from "./chunk-3GMPE2ZR.js";
3
+ } from "./chunk-GGK6TIQZ.js";
4
4
  import {
5
5
  analyze
6
6
  } from "./chunk-DHCVZGHE.js";
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  getCurrentBranch,
3
3
  getDiff,
4
+ getGitHubRemotes,
4
5
  getRepoRoot,
5
6
  listBranches,
6
7
  listCommits
7
- } from "./chunk-3GMPE2ZR.js";
8
+ } from "./chunk-GGK6TIQZ.js";
8
9
  import {
9
10
  analyze
10
11
  } from "./chunk-DHCVZGHE.js";
@@ -79,12 +80,28 @@ async function isServerAlive() {
79
80
  }
80
81
  }
81
82
 
83
+ // packages/core/src/threads.ts
84
+ function lastAuthor(annotation) {
85
+ const replies = annotation.replies ?? [];
86
+ return replies.length > 0 ? replies[replies.length - 1].author : annotation.author ?? "agent";
87
+ }
88
+ function awaitingAgent(annotation) {
89
+ return !annotation.dismissed && lastAuthor(annotation) === "reviewer";
90
+ }
91
+ function lastMessageAt(annotation) {
92
+ const replies = annotation.replies ?? [];
93
+ return replies.length > 0 ? replies[replies.length - 1].createdAt : annotation.createdAt;
94
+ }
95
+ function pickedUpByAgent(annotation, agentReadAt) {
96
+ return agentReadAt !== void 0 && agentReadAt > lastMessageAt(annotation);
97
+ }
98
+
82
99
  // packages/core/src/build-info.ts
83
100
  import fs2 from "fs";
84
101
  import path2 from "path";
85
102
  import { fileURLToPath } from "url";
86
103
  function builtAt() {
87
- return true ? 1789610217504 : null;
104
+ return true ? 1789619773143 : null;
88
105
  }
89
106
  function getBuildInfo() {
90
107
  let dir = path2.dirname(fileURLToPath(import.meta.url));
@@ -122,7 +139,7 @@ import path3 from "path";
122
139
  var ISSUES_NEW_URL = "https://github.com/CodeJonesW/diffprism/issues/new";
123
140
  var MAX_ERROR_CHARS = 1500;
124
141
  function currentVersion() {
125
- return true ? "1.3.2" : "0.0.0-dev";
142
+ return true ? "1.4.0" : "0.0.0-dev";
126
143
  }
127
144
  function describeEnvironment() {
128
145
  return {
@@ -211,15 +228,6 @@ ${errorText}
211
228
  return `${ISSUES_NEW_URL}?${params.toString()}`;
212
229
  }
213
230
 
214
- // packages/core/src/threads.ts
215
- function lastAuthor(annotation) {
216
- const replies = annotation.replies ?? [];
217
- return replies.length > 0 ? replies[replies.length - 1].author : annotation.author ?? "agent";
218
- }
219
- function awaitingAgent(annotation) {
220
- return !annotation.dismissed && lastAuthor(annotation) === "reviewer";
221
- }
222
-
223
231
  // packages/core/src/server-client.ts
224
232
  import { spawn } from "child_process";
225
233
  import fs4 from "fs";
@@ -393,7 +401,7 @@ async function waitForDecision(serverInfo, sessionId, maxWaitMs, pollIntervalMs
393
401
  if (result) {
394
402
  return result;
395
403
  }
396
- const annotationsResponse = await fetch(`${base}/annotations`);
404
+ const annotationsResponse = await fetch(`${base}/annotations?reader=agent`);
397
405
  if (!annotationsResponse.ok) {
398
406
  throw new Error(`Could not read threads for ${sessionId}: server returned ${annotationsResponse.status}`);
399
407
  }
@@ -413,7 +421,7 @@ async function submitReviewToServer(serverInfo, diffRef, options = {}) {
413
421
  if (options.injectedPayload) {
414
422
  payload = options.injectedPayload;
415
423
  } else {
416
- const { getDiff: getDiff2, getCurrentBranch: getCurrentBranch2, detectWorktree } = await import("./src-OZQ32NDN.js");
424
+ const { getDiff: getDiff2, getCurrentBranch: getCurrentBranch2, detectWorktree } = await import("./src-XT4XP6TC.js");
417
425
  const { analyze: analyze2 } = await import("./src-JMPTSU3P.js");
418
426
  const { diffSet, rawDiff } = getDiff2(diffRef, { cwd });
419
427
  if (diffSet.files.length === 0) {
@@ -800,6 +808,14 @@ var IDLE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
800
808
  function touch(session) {
801
809
  session.lastActivityAt = Date.now();
802
810
  }
811
+ function recordAgentRead(session) {
812
+ const previous = session.agentReadAt;
813
+ session.agentReadAt = Date.now();
814
+ touch(session);
815
+ if (session.annotations.some((a) => awaitingAgent(a) && !pickedUpByAgent(a, previous))) {
816
+ broadcastSessionUpdate(session);
817
+ }
818
+ }
803
819
  var sessions = /* @__PURE__ */ new Map();
804
820
  function listedSummaries() {
805
821
  return Array.from(sessions.values()).filter((session) => session.closedAt === void 0).map(toSummary);
@@ -943,7 +959,8 @@ function toSummary(session) {
943
959
  hasNewChanges: session.hasNewChanges,
944
960
  needsAttention: needsAttention(session),
945
961
  diffRef: session.diffRef,
946
- source: session.source
962
+ source: session.source,
963
+ agentReadAt: session.agentReadAt
947
964
  };
948
965
  }
949
966
  function readBody(req) {
@@ -1260,7 +1277,7 @@ async function handleApiRequest(req, res) {
1260
1277
  fetchPullRequest,
1261
1278
  fetchPullRequestDiff,
1262
1279
  normalizePr
1263
- } = await import("./src-DNNNQ2RD.js");
1280
+ } = await import("./src-LLACFIIT.js");
1264
1281
  if (!isPrRef(prUrl)) {
1265
1282
  jsonResponse(res, 400, {
1266
1283
  error: "Invalid PR URL. Expected https://github.com/owner/repo/pull/123 or owner/repo#123"
@@ -1283,20 +1300,7 @@ async function handleApiRequest(req, res) {
1283
1300
  fetchPullRequestDiff(client, owner, repo, prNumber)
1284
1301
  ]);
1285
1302
  const normalized = normalizePr(rawDiff, prMetadata);
1286
- let localRepoPath = null;
1287
- try {
1288
- const { execSync } = await import("child_process");
1289
- const remoteOutput = execSync("git remote -v", {
1290
- cwd: process.cwd(),
1291
- encoding: "utf-8",
1292
- stdio: ["pipe", "pipe", "pipe"]
1293
- });
1294
- const repoPattern = new RegExp(`github\\.com[:/]${owner}/${repo}(\\.git)?\\s`, "i");
1295
- if (repoPattern.test(remoteOutput)) {
1296
- localRepoPath = process.cwd();
1297
- }
1298
- } catch {
1299
- }
1303
+ const localRepoPath = getGitHubRemotes({ cwd: process.cwd() }).includes(`${owner}/${repo}`.toLowerCase()) ? process.cwd() : null;
1300
1304
  const { session, reused } = openSession({
1301
1305
  key: `pr:${owner}/${repo}#${prNumber}`.toLowerCase(),
1302
1306
  repoRoot: localRepoPath ? getRepoRoot({ cwd: localRepoPath }) ?? localRepoPath : null,
@@ -1373,7 +1377,13 @@ async function handleApiRequest(req, res) {
1373
1377
  return true;
1374
1378
  }
1375
1379
  const repoRoot = getRepoRoot({ cwd: rawPath }) ?? path7.resolve(rawPath);
1376
- const matches = Array.from(sessions.values()).filter((session) => session.closedAt === void 0 && session.repoRoot === repoRoot).map(toSummary);
1380
+ const remotes = getGitHubRemotes({ cwd: repoRoot });
1381
+ const matches = Array.from(sessions.values()).filter((session) => {
1382
+ if (session.closedAt !== void 0) return false;
1383
+ if (session.repoRoot === repoRoot) return true;
1384
+ const pr = session.payload.metadata.githubPr;
1385
+ return pr !== void 0 && remotes.includes(`${pr.owner}/${pr.repo}`.toLowerCase());
1386
+ }).map(toSummary);
1377
1387
  jsonResponse(res, 200, { repoRoot, sessions: matches });
1378
1388
  return true;
1379
1389
  }
@@ -1527,6 +1537,9 @@ async function handleApiRequest(req, res) {
1527
1537
  jsonResponse(res, 404, { error: "Session not found" });
1528
1538
  return true;
1529
1539
  }
1540
+ if (new URL(req.url ?? "/", "http://localhost").searchParams.get("reader") === "agent") {
1541
+ recordAgentRead(session);
1542
+ }
1530
1543
  jsonResponse(res, 200, { annotations: session.annotations });
1531
1544
  return true;
1532
1545
  }
@@ -1592,7 +1605,7 @@ async function handleApiRequest(req, res) {
1592
1605
  jsonResponse(res, 400, { error: built.error });
1593
1606
  return true;
1594
1607
  }
1595
- const { resolveGitHubToken, createGitHubClient, submitGitHubReview } = await import("./src-DNNNQ2RD.js");
1608
+ const { resolveGitHubToken, createGitHubClient, submitGitHubReview } = await import("./src-LLACFIIT.js");
1596
1609
  let token;
1597
1610
  try {
1598
1611
  token = resolveGitHubToken();
@@ -2072,6 +2085,7 @@ function mcpToolPermission(name) {
2072
2085
  export {
2073
2086
  readServerFile,
2074
2087
  isServerAlive,
2088
+ awaitingAgent,
2075
2089
  getBuildInfo,
2076
2090
  describeVersion,
2077
2091
  DEFAULT_DIFF_REF,
@@ -2083,7 +2097,6 @@ export {
2083
2097
  REPORT_HINT,
2084
2098
  buildFeedbackUrl,
2085
2099
  startGlobalServer,
2086
- awaitingAgent,
2087
2100
  ensureServer,
2088
2101
  ReviewTimeoutError,
2089
2102
  ReviewerAskedError,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  demo
3
- } from "./chunk-XGQRRG2H.js";
4
- import "./chunk-USOWTWJD.js";
5
- import "./chunk-3GMPE2ZR.js";
3
+ } from "./chunk-6P6QJPWT.js";
4
+ import "./chunk-SCQFIDIK.js";
5
+ import "./chunk-GGK6TIQZ.js";
6
6
  import "./chunk-DHCVZGHE.js";
7
7
  import "./chunk-JSBRDJBE.js";
8
8
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isPrRef
3
- } from "./chunk-EPU4F7WT.js";
3
+ } from "./chunk-RFDSC6MC.js";
4
4
  import {
5
5
  DEFAULT_DIFF_REF,
6
6
  DIFF_REF_DESCRIPTION,
@@ -14,10 +14,10 @@ import {
14
14
  recordError,
15
15
  submitReviewToServer,
16
16
  waitForDecision
17
- } from "./chunk-USOWTWJD.js";
17
+ } from "./chunk-SCQFIDIK.js";
18
18
  import {
19
19
  getDiff
20
- } from "./chunk-3GMPE2ZR.js";
20
+ } from "./chunk-GGK6TIQZ.js";
21
21
  import {
22
22
  analyze
23
23
  } from "./chunk-DHCVZGHE.js";
@@ -73,7 +73,7 @@ ${candidates}`
73
73
  }
74
74
  async function readThreads(serverInfo, sessionId) {
75
75
  const response = await fetch(
76
- `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations`
76
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations?reader=agent`
77
77
  );
78
78
  if (!response.ok) {
79
79
  return null;
@@ -354,7 +354,7 @@ ${REPORT_HINT}`);
354
354
  async ({ session_id, repo_path }) => withSession({ session_id, repo_path }, async ({ serverInfo, sessionId }) => {
355
355
  const [sessionResponse, annotationsResponse] = await Promise.all([
356
356
  fetch(`http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}`),
357
- fetch(`http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations`)
357
+ fetch(`http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations?reader=agent`)
358
358
  ]);
359
359
  if (!sessionResponse.ok) {
360
360
  return toolError(`Session not found: ${sessionId}`);
@@ -7,8 +7,8 @@ import {
7
7
  parsePrRef,
8
8
  resolveGitHubToken,
9
9
  submitGitHubReview
10
- } from "./chunk-EPU4F7WT.js";
11
- import "./chunk-3GMPE2ZR.js";
10
+ } from "./chunk-RFDSC6MC.js";
11
+ import "./chunk-GGK6TIQZ.js";
12
12
  import "./chunk-DHCVZGHE.js";
13
13
  import "./chunk-JSBRDJBE.js";
14
14
  export {
@@ -3,17 +3,19 @@ import {
3
3
  getCurrentBranch,
4
4
  getDiff,
5
5
  getGitDiff,
6
+ getGitHubRemotes,
6
7
  getRepoRoot,
7
8
  listBranches,
8
9
  listCommits,
9
10
  parseDiff
10
- } from "./chunk-3GMPE2ZR.js";
11
+ } from "./chunk-GGK6TIQZ.js";
11
12
  import "./chunk-JSBRDJBE.js";
12
13
  export {
13
14
  detectWorktree,
14
15
  getCurrentBranch,
15
16
  getDiff,
16
17
  getGitDiff,
18
+ getGitHubRemotes,
17
19
  getRepoRoot,
18
20
  listBranches,
19
21
  listCommits,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {