diffprism 1.2.0 → 1.3.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
@@ -218,6 +218,26 @@ pnpm run build
218
218
  pnpm cli review --staged # Run CLI from source
219
219
  ```
220
220
 
221
+ ### Dogfooding a checkout
222
+
223
+ To try unreleased changes in another project without publishing, point your global `diffprism` at a checkout:
224
+
225
+ ```bash
226
+ git clone https://github.com/CodeJonesW/diffprism.git diffprism-dogfood
227
+ cd diffprism-dogfood && pnpm install && pnpm build && npm link
228
+ diffprism --version # 1.2.0 (dev build — /path/to/diffprism-dogfood)
229
+ ```
230
+
231
+ Then, for each change you want to try:
232
+
233
+ ```bash
234
+ git fetch && git checkout <branch> && git pull && pnpm build
235
+ ```
236
+
237
+ The next `diffprism` command — a review, the commit gate — replaces the running server with the new build, unless a review is open in it. The dashboard picks up a rebuilt UI on reload. Go back to the release with `npm install -g diffprism@latest`.
238
+
239
+ A server is only ever replaced by a **newer** build. Claude Code keeps `diffprism serve` running on the build it started with until you restart it, so restart Claude Code to put its MCP tools on the new build too.
240
+
221
241
  ### Project Structure
222
242
 
223
243
  ```
package/dist/bin.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-EPU4F7WT.js";
6
6
  import {
7
7
  demo
8
- } from "./chunk-FONA4U6B.js";
8
+ } from "./chunk-7YL3D42S.js";
9
9
  import {
10
10
  COMMIT_GATE_DIFF_REF,
11
11
  DEFAULT_DIFF_REF,
@@ -26,7 +26,7 @@ import {
26
26
  recordError,
27
27
  startGlobalServer,
28
28
  submitReviewToServer
29
- } from "./chunk-F6O5YP3I.js";
29
+ } from "./chunk-MSNGZDVF.js";
30
30
  import {
31
31
  getDiff
32
32
  } from "./chunk-3GMPE2ZR.js";
@@ -79,6 +79,9 @@ ${REPORT_HINT}`);
79
79
  }
80
80
  const where = data.annotation ? ` on ${data.annotation.file}:${data.annotation.line}` : "";
81
81
  console.log(`Replied${where}. It shows in the review now.`);
82
+ console.log(
83
+ "The review is still open. Go back to waiting for the decision now: run the same command that opened it again (`git commit` or `diffprism review`). It returns the decision, or the reviewer's next question. Don't ask them in the terminal."
84
+ );
82
85
  }
83
86
  function fail(message2) {
84
87
  console.error(message2);
@@ -136,7 +139,7 @@ ${REPORT_HINT}`);
136
139
  if (err instanceof ReviewerAskedError) {
137
140
  printQuestions(err.sessionId, err.threads);
138
141
  fail2(
139
- "Commit blocked: the reviewer asked you something before deciding. Answer each question with the command under it \u2014 change the code too if that's what they asked for \u2014 then run git commit again. The review stays open and the decision still comes."
142
+ "Commit blocked: the reviewer asked you something before deciding. Answer each question with the command under it \u2014 change the code too if that's what they asked for \u2014 then run git commit again straight away to keep waiting. The review stays open, the reviewer may ask more, and the decision still comes. Don't stop to ask them in the terminal."
140
143
  );
141
144
  return;
142
145
  }
@@ -369,7 +372,7 @@ async function reviewLocalFlow(diffRef, flags) {
369
372
  if (err instanceof ReviewerAskedError) {
370
373
  printQuestions(err.sessionId, err.threads);
371
374
  console.error(
372
- "The reviewer asked something before deciding. Answer each question with the command under it, then run diffprism review again \u2014 the review stays open."
375
+ "The reviewer asked something before deciding. Answer each question with the command under it, then run diffprism review again straight away to keep waiting \u2014 the review stays open, and the reviewer may ask more. Don't stop to ask them in the terminal."
373
376
  );
374
377
  process.exit(1);
375
378
  }
@@ -716,7 +719,7 @@ async function setupInteractive(flags) {
716
719
  }
717
720
  async function runDemo(dev) {
718
721
  console.log("");
719
- const { demo: demo2 } = await import("./demo-VNWYEDZ3.js");
722
+ const { demo: demo2 } = await import("./demo-376MCAK4.js");
720
723
  await demo2({ dev });
721
724
  }
722
725
  async function setupBatch(flags) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ensureServer,
3
3
  submitReviewToServer
4
- } from "./chunk-F6O5YP3I.js";
4
+ } from "./chunk-MSNGZDVF.js";
5
5
  import {
6
6
  parseDiff
7
7
  } from "./chunk-3GMPE2ZR.js";
@@ -79,15 +79,13 @@ async function isServerAlive() {
79
79
  }
80
80
  }
81
81
 
82
- // packages/core/src/diff-scope.ts
83
- var DEFAULT_DIFF_REF = "working-copy";
84
- var COMMIT_GATE_DIFF_REF = "staged";
85
- var DIFF_REF_DESCRIPTION = 'Which changes to review. "working-copy" (the default): everything not yet committed, staged and unstaged shown as separate groups. "staged": only what the next commit would contain. "unstaged": only edits not yet staged. Or a ref range such as "HEAD~3..HEAD" or "main..feature".';
86
-
87
82
  // packages/core/src/build-info.ts
88
83
  import fs2 from "fs";
89
84
  import path2 from "path";
90
85
  import { fileURLToPath } from "url";
86
+ function builtAt() {
87
+ return true ? 1789607742599 : null;
88
+ }
91
89
  function getBuildInfo() {
92
90
  let dir = path2.dirname(fileURLToPath(import.meta.url));
93
91
  while (dir !== path2.dirname(dir)) {
@@ -112,6 +110,11 @@ function describeVersion(version) {
112
110
  return info.dev ? `${version} (dev build \u2014 ${info.root})` : version;
113
111
  }
114
112
 
113
+ // packages/core/src/diff-scope.ts
114
+ var DEFAULT_DIFF_REF = "working-copy";
115
+ var COMMIT_GATE_DIFF_REF = "staged";
116
+ var DIFF_REF_DESCRIPTION = 'Which changes to review. "working-copy" (the default): everything not yet committed, staged and unstaged shown as separate groups. "staged": only what the next commit would contain. "unstaged": only edits not yet staged. Or a ref range such as "HEAD~3..HEAD" or "main..feature".';
117
+
115
118
  // packages/core/src/feedback.ts
116
119
  import fs3 from "fs";
117
120
  import os2 from "os";
@@ -119,7 +122,7 @@ import path3 from "path";
119
122
  var ISSUES_NEW_URL = "https://github.com/CodeJonesW/diffprism/issues/new";
120
123
  var MAX_ERROR_CHARS = 1500;
121
124
  function currentVersion() {
122
- return true ? "1.2.0" : "0.0.0-dev";
125
+ return true ? "1.3.0" : "0.0.0-dev";
123
126
  }
124
127
  function describeEnvironment() {
125
128
  return {
@@ -226,7 +229,22 @@ import { fileURLToPath as fileURLToPath2 } from "url";
226
229
  async function ensureServer(options = {}) {
227
230
  const existing = await isServerAlive();
228
231
  if (existing) {
229
- return existing;
232
+ const decision = await decideOnRunningServer(existing, builtAt());
233
+ if (decision.action === "keep") {
234
+ return existing;
235
+ }
236
+ if (decision.action === "keep-busy") {
237
+ if (!options.silent) {
238
+ console.error(
239
+ `The DiffPrism server (PID ${existing.pid}) runs an older build, but ${decision.openReviews} review${decision.openReviews === 1 ? " is" : "s are"} open in it, so it stays up. Finish or close ${decision.openReviews === 1 ? "it" : "them"} and the next command starts the new build \u2014 or run \`diffprism server stop\`.`
240
+ );
241
+ }
242
+ return existing;
243
+ }
244
+ if (!options.silent) {
245
+ console.error(`Replacing the DiffPrism server (PID ${existing.pid}): it runs an older build than this command.`);
246
+ }
247
+ await stopServer(existing);
230
248
  }
231
249
  const spawnArgs = options.spawnCommand ?? buildDefaultSpawnCommand(options);
232
250
  const logDir = path4.join(os3.homedir(), ".diffprism");
@@ -256,6 +274,44 @@ async function ensureServer(options = {}) {
256
274
  `DiffPrism server failed to start within ${timeoutMs / 1e3}s. Check logs at ${logPath}`
257
275
  );
258
276
  }
277
+ async function decideOnRunningServer(server, ownBuiltAt) {
278
+ if (ownBuiltAt === null) return { action: "keep" };
279
+ if (server.builtAt !== void 0 && server.builtAt >= ownBuiltAt) return { action: "keep" };
280
+ const response = await fetch(`http://localhost:${server.httpPort}/api/reviews`);
281
+ if (!response.ok) {
282
+ throw new Error(`Could not list reviews on the running DiffPrism server: it returned ${response.status}`);
283
+ }
284
+ const { sessions: sessions2 } = await response.json();
285
+ const openReviews = sessions2.filter((s) => s.status === "pending" || s.status === "in_review").length;
286
+ return openReviews > 0 ? { action: "keep-busy", openReviews } : { action: "replace" };
287
+ }
288
+ async function stopServer(server, timeoutMs = 5e3) {
289
+ try {
290
+ process.kill(server.pid, "SIGTERM");
291
+ } catch {
292
+ }
293
+ const start = Date.now();
294
+ while (Date.now() - start < timeoutMs) {
295
+ if (!isProcessRunning(server.pid)) {
296
+ if (readServerFile()?.pid === server.pid) {
297
+ removeServerFile();
298
+ }
299
+ return;
300
+ }
301
+ await new Promise((resolve) => setTimeout(resolve, 100));
302
+ }
303
+ throw new Error(
304
+ `The old DiffPrism server (PID ${server.pid}) did not stop within ${timeoutMs / 1e3}s. Stop it with \`diffprism server stop\` and try again.`
305
+ );
306
+ }
307
+ function isProcessRunning(pid) {
308
+ try {
309
+ process.kill(pid, 0);
310
+ return true;
311
+ } catch {
312
+ return false;
313
+ }
314
+ }
259
315
  function buildDefaultSpawnCommand(options) {
260
316
  const thisFile = fileURLToPath2(import.meta.url);
261
317
  const thisDir = path4.dirname(thisFile);
@@ -1913,6 +1969,10 @@ async function startGlobalServer(options = {}) {
1913
1969
  pid: process.pid,
1914
1970
  startedAt: Date.now()
1915
1971
  };
1972
+ const serverBuiltAt = builtAt();
1973
+ if (serverBuiltAt !== null) {
1974
+ serverInfo.builtAt = serverBuiltAt;
1975
+ }
1916
1976
  writeServerFile(serverInfo);
1917
1977
  if (!silent) {
1918
1978
  console.log(`
@@ -1993,11 +2053,11 @@ function mcpToolPermission(name) {
1993
2053
  export {
1994
2054
  readServerFile,
1995
2055
  isServerAlive,
2056
+ getBuildInfo,
2057
+ describeVersion,
1996
2058
  DEFAULT_DIFF_REF,
1997
2059
  COMMIT_GATE_DIFF_REF,
1998
2060
  DIFF_REF_DESCRIPTION,
1999
- getBuildInfo,
2000
- describeVersion,
2001
2061
  currentVersion,
2002
2062
  recordError,
2003
2063
  readLastError,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  demo
3
- } from "./chunk-FONA4U6B.js";
4
- import "./chunk-F6O5YP3I.js";
3
+ } from "./chunk-7YL3D42S.js";
4
+ import "./chunk-MSNGZDVF.js";
5
5
  import "./chunk-3GMPE2ZR.js";
6
6
  import "./chunk-DHCVZGHE.js";
7
7
  import "./chunk-JSBRDJBE.js";
@@ -14,7 +14,7 @@ import {
14
14
  recordError,
15
15
  submitReviewToServer,
16
16
  waitForDecision
17
- } from "./chunk-F6O5YP3I.js";
17
+ } from "./chunk-MSNGZDVF.js";
18
18
  import {
19
19
  getDiff
20
20
  } from "./chunk-3GMPE2ZR.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {