diffing 0.9.0 → 0.10.1

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
@@ -29,14 +29,19 @@ diffing
29
29
  ```
30
30
  This instantly spins up a local server, establishes an active repository watcher, and opens your default browser to an interactive code review dashboard.
31
31
 
32
- Prefer the terminal? Add `--tui` for a native-Rust terminal UI — same review flow, no browser:
32
+ Prefer the terminal? After building or separately installing the native binary
33
+ as described below, add `--tui` for the same review flow without a browser:
34
+
33
35
  ```bash
34
36
  diffing --tui
35
37
  ```
38
+
36
39
  > [!WARNING]
37
40
  > The TUI is **experimental**. The interface, keymap, and `server.json`
38
41
  > (`mode: "tui"`) on-disk format may change in a minor release. The web UI is
39
- > the supported path for production workflows. See
42
+ > the supported path for production workflows. The v0.10.0 npm package does
43
+ > **not** bundle a platform-specific `diffing-tui` executable; `--tui`
44
+ > requires a source build or a separately installed binary on `PATH`. See
40
45
  > [Native Terminal UI (TUI)](#native-terminal-ui-tui) for the full feature set,
41
46
  > keymap, and stability notes.
42
47
 
@@ -175,8 +180,18 @@ A vim-style status bar at the bottom displays the current mode (NORMAL/INSERT),
175
180
  > please open an issue before depending on the TUI for CI / agent automation.
176
181
  > The web review flow, plan review, and PR review are unaffected.
177
182
 
183
+ > [!IMPORTANT]
184
+ > **Distribution status in v0.10.0:** the npm package contains the Node CLI and
185
+ > web UI, but not a platform-specific `diffing-tui` executable. Build the
186
+ > binary from a source checkout or install it separately on `PATH`. diffing
187
+ > does not compile Rust during npm installation. If no compatible binary is
188
+ > found, `diffing --tui` prints a fallback notice and runs the normal terminal
189
+ > diff instead.
190
+
178
191
  `diffing --tui` opens an **opt-in native-Rust terminal interface** that mirrors the local code-review workflow — no browser or Electron. The renderer and headless tools share one sparse diff index. A random-port loopback API is published through `server.json` with `mode: "tui"` and a per-session capability, so local agents can inspect a large diff without receiving the whole patch.
179
192
 
193
+ Its diff-first **Gridline** design system uses a one-row command header, border-light file navigation, a dedicated review gutter, high-contrast semantic tokens, and keyboard-visible focus rails. Empty review drawers collapse automatically. Wide terminals can show files, diff, and review together; medium layouts place an active review below the diff; compact layouts show one focused workspace at a time. Press `,` for persisted Settings, including **Single file** versus virtualized **Continuous files**, split/unified layout, wrap, tab size, line numbers, review drawer, optional language intelligence, and theme.
194
+
180
195
  The default `diffing` behaviour is **byte-identical** with and without `--tui` — the TUI is strictly opt-in. If the env cannot support a TUI (piped stdin, CI, no raw mode) or the binary is missing, you get a one-line stderr note and the normal `git diff` output. The web mode is also unaffected by the TUI build; the same `diffing` install serves either.
181
196
 
182
197
  ```bash
@@ -192,12 +207,13 @@ diffing --tui -- -- src/ # Limit a TUI review to a directory
192
207
  **Features**
193
208
 
194
209
  - **Disk-backed streaming index** — Git output is parsed as bytes into sparse file/hunk/checkpoint metadata. The first partial generation is usable during ingestion; neither the TUI nor an agent needs to retain the full patch in memory.
195
- - **Viewport-only rendering** — only visible rows are sought, decoded, syntax-highlighted, and converted to terminal cells. Unified and split layouts, horizontal scrolling, wrapping, binary markers, untracked files, and live working-tree refresh are supported.
196
- - **Vim-style file tree & keymap** — numeric counts plus `j/k`, `gg/G`, `Ctrl-d/u`, `J/K`, `]h/[h`, `]c/[c`, `zz`, `h/l`, `/`, `n/N`, `:`, `Tab`, `v`, `w`, `m`, `t`, and `?`. `Esc` cancels a mode; `q` or `Ctrl-C` quits.
197
- - **Full comment CRUD** — mirrors `src/lib/comments.ts` byte-for-byte; the comment tracker at the bottom lists open / replied / resolved threads with click-to-jump navigation.
210
+ - **Viewport-only rendering** — only visible rows are sought, decoded, theme-aware syntax-highlighted, and converted to terminal cells. Highlighting is available in both unified and split layouts, with token contrast corrected against semantic addition/deletion backgrounds. Single/continuous file display, horizontal scrolling, wrapping, binary markers, untracked files, and live working-tree refresh are supported.
211
+ - **Optional local language intelligence** — Auto mode lazily discovers an existing `rust-analyzer`, `typescript-language-server`, `pyright-langserver`, `gopls`, or `clangd` on `PATH`; diffing never downloads a server or sends source off-machine. Diagnostics use lettered `E/W/I/H` gutter marks and appear in the status line, `gh` opens hover, `gd` follows definitions that are visible in the current diff, and `Alt-h/l` moves the symbol column. Turn the feature off from Settings.
212
+ - **Vim-style file tree & keymap** — numeric counts plus `j/k`, `gg/G`, `Ctrl-d/u`, `J/K`, `]h/[h`, `]c/[c`, `zz`, `h/l`, `gh`, `gd`, `/`, `n/N`, `f`, `a`, `:`, `Tab`, `v`, `w`, `m`, `t`, `,`, and `?`. `Esc` cancels a mode; `q` or `Ctrl-C` quits. Single/continuous file display and language intelligence are changed from Settings.
213
+ - **Review-complete comments** — line, same-side range, and file-level threads share the web JSON schema, including severity. The drawer filters open/replied/resolved and blocking/question/nit/praise threads; the diff gutter distinguishes their states without relying on color alone.
198
214
  - **Multi-line `tui-textarea` form** with markdown rendering in the preview pane.
199
215
  - **Live updates** via `notify` watcher on `comments.json` and the repo working tree — write a comment in another window and it appears immediately.
200
- - **Send review & agent handoff** — verdict radios + general-comment popover with a live XML preview. `Ctrl-S` persists the review, copies it to the clipboard when available, and immediately wakes every `diffing await-review` waiter. The status bar reports real waiter state.
216
+ - **Send review & agent handoff** — compact verdict + general-comment popover with an unviewed-files guard. `Ctrl-S` persists the review, copies its XML payload to the clipboard when available, and immediately wakes every `diffing await-review` waiter. Waiting-agent state appears only while a waiter is active.
201
217
  - **Headless, token-bounded inspection** — `diffing inspect summary|files|hunks|slice|search` and the equivalent MCP tools page the same index with strict row/byte limits, generation checks, and compact JSON. Every request is loopback-only and requires the session capability.
202
218
  - **Cross-platform** — macOS, Linux, and Windows are all first-class. The TUI liveness probe uses `kill(pid, 0)` on Unix and `tasklist /NH /FO CSV` on Windows. Clipboard works on Wayland (`wl-copy`), X11 (`xclip` / `xsel`), macOS (`pbcopy`), and Windows (`clip.exe` / PowerShell `Set-Clipboard`).
203
219
 
@@ -215,11 +231,19 @@ diffing inspect search "unsafe" --limit 25
215
231
  **Building the binary locally**
216
232
 
217
233
  ```bash
218
- pnpm build:tui # debug build → target/debug/diffing-tui
219
- pnpm build:tui --release # release build → target/release/diffing-tui
234
+ pnpm build:tui:debug # debug build → target/debug/diffing-tui
235
+ pnpm build:tui # release build → target/release/diffing-tui
220
236
  ```
221
237
 
222
- The CLI auto-discovers the binary in the following search order: sibling of `dist/cli.mjs`, `bin/`, `target/release/`, `target/debug/`, then `$PATH`. A `cargo build` (debug) workflow is supported out of the box; you don't need `--release` just to use the TUI.
238
+ The CLI auto-discovers the binary in the following search order: sibling of
239
+ `dist/cli.mjs`, `bin/`, `target/release/`, `target/debug/`, then `$PATH`. A
240
+ `cargo build -p diffing-tui` debug workflow is supported out of the box; you
241
+ do not need a release build just to use the TUI locally. The sibling and
242
+ `bin/` locations are reserved for future packaged-binary distribution; they
243
+ are not populated by the v0.10.0 npm package. A CLI run from this source
244
+ checkout discovers the binary under `target/`; to use the globally installed
245
+ npm CLI from arbitrary repositories, copy or symlink the resulting executable
246
+ into a directory on `PATH`.
223
247
 
224
248
  ---
225
249
 
@@ -643,7 +643,7 @@ async function plan(args) {
643
643
  }
644
644
  }
645
645
  async function doctor() {
646
- const { runDoctor, formatDoctorReport } = await import("./doctor-CmP0u7-d.mjs");
646
+ const { runDoctor, formatDoctorReport } = await import("./doctor-DRWEgsMs.mjs");
647
647
  const report = await runDoctor({
648
648
  cwd: process.cwd(),
649
649
  cliImportMetaUrl: import.meta.url
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as intoShowMode, n as runTerminalDiff, o as parseDiffOptions, r as validateEnvironment, s as printHelp, t as startServer } from "./server-CczuXSWo.mjs";
2
+ import { a as intoShowMode, n as runTerminalDiff, o as parseDiffOptions, r as validateEnvironment, s as printHelp, t as startServer } from "./server-CRk40DPV.mjs";
3
3
  import { p as getRepoRoot } from "./git-Czo96ymM.mjs";
4
4
  import { t as loadSettings } from "./settings-BVnzGFL7.mjs";
5
5
  import { i as readServerLock, n as diffScopeKey, o as removeServerLockIfOwned, r as isLockAlive, s as writeServerLock, t as acquireServerStartupLease } from "./server-lock-uqSdDYeR.mjs";
@@ -475,11 +475,11 @@ See docs/cli.md §5 (MCP) for the full tool table.`);
475
475
  console.error("diffing mcp: --repo must be an absolute path");
476
476
  process.exit(5);
477
477
  }
478
- const { startMcpServer } = await import("./mcp-CqxXILYL.mjs");
478
+ const { startMcpServer } = await import("./mcp-D4cwru99.mjs");
479
479
  await startMcpServer({ repoPath });
480
480
  await new Promise(() => {});
481
481
  }
482
- const { runSubcommand } = await import("./cli-agent-CLrK54k2.mjs");
482
+ const { runSubcommand } = await import("./cli-agent-7DUbPdeW.mjs");
483
483
  process.exit(await runSubcommand(args[0], args.slice(1)));
484
484
  }
485
485
  let showSubcommand = false;
@@ -1,5 +1,5 @@
1
1
  import { r as getSearchStatus, t as loadSettings } from "./settings-BVnzGFL7.mjs";
2
- import { h as readGithubToken, o as detectGhCli } from "./github-oCZx5aim.mjs";
2
+ import { b as readGithubToken, o as detectGhCli } from "./github-C27GDU9I.mjs";
3
3
  import { i as readServerLock, r as isLockAlive } from "./server-lock-uqSdDYeR.mjs";
4
4
  import { t as findTuiBinary } from "./find-tui-binary-CRpbYXLC.mjs";
5
5
  import { execFileSync } from "node:child_process";
@@ -0,0 +1,2 @@
1
+ import { S as replyToPrComment, d as fetchPrChecks, n as buildReviewPayload } from "./github-C27GDU9I.mjs";
2
+ export { buildReviewPayload, fetchPrChecks, replyToPrComment };
@@ -104,13 +104,78 @@ function readGithubToken() {
104
104
  const env = process.env;
105
105
  return env.GH_TOKEN || env.GITHUB_TOKEN || env.GITHUB_API_TOKEN || null;
106
106
  }
107
+ /** True when the host is github.com (or unspecified, which we treat the same). */
108
+ function isGithubDotCom(host) {
109
+ return !host || host === "github.com";
110
+ }
111
+ /**
112
+ * `gh -R` selector: `OWNER/REPO` on github.com, `HOST/OWNER/REPO` on GHES.
113
+ * See `gh help environment` / `--repo [HOST/]OWNER/REPO`.
114
+ */
115
+ function ghRepoSelector(resolved) {
116
+ if (isGithubDotCom(resolved.host)) return `${resolved.owner}/${resolved.repo}`;
117
+ return `${resolved.host}/${resolved.owner}/${resolved.repo}`;
118
+ }
119
+ /** Extra argv for `gh api` / GraphQL when targeting a non-github.com host. */
120
+ function ghHostnameArgs(resolved) {
121
+ if (isGithubDotCom(resolved.host)) return [];
122
+ return ["--hostname", resolved.host];
123
+ }
124
+ /** REST API origin for token-based fetch (GHES uses `/api/v3`). */
125
+ function githubApiBase(host) {
126
+ if (isGithubDotCom(host)) return "https://api.github.com";
127
+ return `${(host.includes("://") ? host : `https://${host}`).replace(/\/$/, "")}/api/v3`;
128
+ }
129
+ /** Build a ResolvedPr from a persisted PR session (host optional for legacy JSON). */
130
+ function resolvedFromSession(session) {
131
+ return {
132
+ owner: session.owner,
133
+ repo: session.repo,
134
+ pullNumber: session.pullNumber,
135
+ ref: session.ref,
136
+ host: session.host
137
+ };
138
+ }
139
+ /**
140
+ * Parse a git remote URL into `{ host, owner, repo }`.
141
+ * Supports scp-like SSH, `ssh://`, and `https://` for github.com and GHES.
142
+ */
143
+ function parseGitRemoteUrl(url) {
144
+ const trimmed = url.trim();
145
+ if (!trimmed) return null;
146
+ if (!trimmed.includes("://")) {
147
+ const scp = /^(?:[^@\s]+@)?([^:\s]+):([^/\s]+)\/([^/\s]+?)(?:\.git)?\s*$/.exec(trimmed);
148
+ if (scp) return {
149
+ host: scp[1],
150
+ owner: scp[2],
151
+ repo: scp[3]
152
+ };
153
+ }
154
+ try {
155
+ const normalized = trimmed.replace(/^git\+/, "");
156
+ const u = new URL(normalized);
157
+ const host = u.host;
158
+ const parts = u.pathname.replace(/^\/+/, "").split("/").filter(Boolean);
159
+ if (host && parts.length >= 2) {
160
+ const owner = parts[0];
161
+ const repo = parts[1].replace(/\.git$/i, "");
162
+ if (owner && repo) return {
163
+ host,
164
+ owner,
165
+ repo
166
+ };
167
+ }
168
+ } catch {}
169
+ return null;
170
+ }
107
171
  function parsePrRef(input, cwdRepo) {
108
172
  const trimmed = input.trim();
109
- const urlMatch = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/i.exec(trimmed);
173
+ const urlMatch = /^https?:\/\/([^/]+)\/([^/]+)\/([^/]+)\/pull\/(\d+)/i.exec(trimmed);
110
174
  if (urlMatch) return {
111
- owner: urlMatch[1],
112
- repo: urlMatch[2],
113
- pullNumber: Number(urlMatch[3]),
175
+ host: urlMatch[1],
176
+ owner: urlMatch[2],
177
+ repo: urlMatch[3],
178
+ pullNumber: Number(urlMatch[4]),
114
179
  ref: trimmed
115
180
  };
116
181
  const shorthand = /^([^/\s]+)\/([^#\s]+)#(\d+)$/.exec(trimmed);
@@ -118,21 +183,24 @@ function parsePrRef(input, cwdRepo) {
118
183
  owner: shorthand[1],
119
184
  repo: shorthand[2],
120
185
  pullNumber: Number(shorthand[3]),
121
- ref: trimmed
186
+ ref: trimmed,
187
+ host: cwdRepo?.host
122
188
  };
123
189
  const bare = /^#?(\d+)$/.exec(trimmed);
124
190
  if (bare && cwdRepo) return {
125
191
  owner: cwdRepo.owner,
126
192
  repo: cwdRepo.repo,
127
193
  pullNumber: Number(bare[1]),
128
- ref: trimmed
194
+ ref: trimmed,
195
+ host: cwdRepo.host
129
196
  };
130
197
  if (bare) throw new Error(`Cannot resolve bare PR number "${trimmed}" — run from inside the target repo, or pass a full URL or \`owner/repo#1234\`.`);
131
198
  throw new Error(`Unrecognised PR ref: ${trimmed}`);
132
199
  }
133
200
  /**
134
- * Best-effort: derive `{ owner, repo }` for the current working directory
135
- * from `git remote get-url origin`. Falls back to throwing.
201
+ * Best-effort: derive `{ host, owner, repo }` for the current working directory
202
+ * from `git remote get-url origin`, then fall back to `gh repo view` (which
203
+ * understands GHES remotes when `gh` is logged in to that host).
136
204
  */
137
205
  async function detectCwdRepo() {
138
206
  try {
@@ -141,12 +209,29 @@ async function detectCwdRepo() {
141
209
  "get-url",
142
210
  "origin"
143
211
  ], { encoding: "utf-8" });
144
- const url = stdout.trim();
145
- const m = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\s*$/.exec(url);
146
- if (m) return {
147
- owner: m[1],
148
- repo: m[2]
149
- };
212
+ const parsed = parseGitRemoteUrl(stdout.trim());
213
+ if (parsed) return parsed;
214
+ } catch {}
215
+ try {
216
+ const { stdout } = await execFileAsync("gh", [
217
+ "repo",
218
+ "view",
219
+ "--json",
220
+ "nameWithOwner,url"
221
+ ], { encoding: "utf-8" });
222
+ const data = JSON.parse(stdout);
223
+ if (data.url) {
224
+ const fromUrl = parseGitRemoteUrl(data.url);
225
+ if (fromUrl) return fromUrl;
226
+ }
227
+ if (data.nameWithOwner) {
228
+ const [owner, repo] = data.nameWithOwner.split("/");
229
+ if (owner && repo) return {
230
+ owner,
231
+ repo,
232
+ host: "github.com"
233
+ };
234
+ }
150
235
  } catch {}
151
236
  return null;
152
237
  }
@@ -155,7 +240,7 @@ async function detectCwdRepo() {
155
240
  * Throws with a user-readable message on failure.
156
241
  */
157
242
  async function fetchPrMetadataViaGh(resolved) {
158
- const args = ["-R", `${resolved.owner}/${resolved.repo}`];
243
+ const args = ["-R", ghRepoSelector(resolved)];
159
244
  let metaJson;
160
245
  try {
161
246
  const { stdout } = await execFileAsync("gh", [
@@ -218,6 +303,7 @@ async function fetchExistingReviewsViaGh(resolved) {
218
303
  try {
219
304
  const { stdout } = await execFileAsync("gh", [
220
305
  "api",
306
+ ...ghHostnameArgs(resolved),
221
307
  reviewsEndpoint,
222
308
  "--paginate"
223
309
  ], {
@@ -253,7 +339,6 @@ async function fetchExistingReviewsViaGh(resolved) {
253
339
  }
254
340
  }
255
341
  async function fetchExistingCommentsViaGh(resolved, existingReviews) {
256
- `${resolved.owner}${resolved.repo}`;
257
342
  const endpoint = `repos/${resolved.owner}/${resolved.repo}/pulls/${resolved.pullNumber}/comments`;
258
343
  const recentReviews = existingReviews ?? await fetchExistingReviewsViaGh(resolved);
259
344
  const reviewStateById = /* @__PURE__ */ new Map();
@@ -262,6 +347,7 @@ async function fetchExistingCommentsViaGh(resolved, existingReviews) {
262
347
  try {
263
348
  const { stdout } = await execFileAsync("gh", [
264
349
  "api",
350
+ ...ghHostnameArgs(resolved),
265
351
  `${endpoint}?per_page=100`,
266
352
  "--paginate"
267
353
  ], {
@@ -354,6 +440,7 @@ async function fetchReviewThreadStateViaGh(resolved) {
354
440
  const { stdout } = await execWithInput("gh", [
355
441
  "api",
356
442
  "graphql",
443
+ ...ghHostnameArgs(resolved),
357
444
  "--input",
358
445
  "-"
359
446
  ], JSON.stringify({
@@ -471,12 +558,13 @@ function stripBPrefix(path) {
471
558
  }
472
559
  async function submitViaGh(input) {
473
560
  const payload = buildReviewPayload(input);
474
- `${input.resolved.owner}${input.resolved.repo}`;
561
+ const endpoint = `repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`;
475
562
  const args = [
476
563
  "api",
564
+ ...ghHostnameArgs(input.resolved),
477
565
  "--method",
478
566
  "POST",
479
- `repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`,
567
+ endpoint,
480
568
  "-H",
481
569
  "Accept: application/vnd.github+json",
482
570
  "--input",
@@ -507,7 +595,7 @@ async function submitViaGh(input) {
507
595
  }
508
596
  async function submitViaToken(input, token) {
509
597
  const payload = buildReviewPayload(input);
510
- const url = `https://api.github.com/repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`;
598
+ const url = `${githubApiBase(input.resolved.host)}/repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`;
511
599
  try {
512
600
  const res = await fetch(url, {
513
601
  method: "POST",
@@ -551,9 +639,11 @@ async function submitViaToken(input, token) {
551
639
  async function fetchPrChecks(resolved, headSha) {
552
640
  const checks = [];
553
641
  try {
642
+ const endpoint = `repos/${resolved.owner}/${resolved.repo}/commits/${headSha}/check-runs?per_page=50`;
554
643
  const { stdout } = await execFileAsync("gh", [
555
644
  "api",
556
- `repos/${resolved.owner}/${resolved.repo}/commits/${headSha}/check-runs?per_page=50`,
645
+ ...ghHostnameArgs(resolved),
646
+ endpoint,
557
647
  "-H",
558
648
  "Accept: application/vnd.github+json"
559
649
  ], {
@@ -597,6 +687,7 @@ async function replyToPrComment(input) {
597
687
  try {
598
688
  const { stdout } = await execWithInput("gh", [
599
689
  "api",
690
+ ...ghHostnameArgs(input.resolved),
600
691
  "--method",
601
692
  "POST",
602
693
  endpoint,
@@ -638,6 +729,7 @@ async function updatePrReviewComment(input) {
638
729
  try {
639
730
  await execWithInput("gh", [
640
731
  "api",
732
+ ...ghHostnameArgs(input.resolved),
641
733
  "--method",
642
734
  "PATCH",
643
735
  endpoint,
@@ -659,6 +751,7 @@ async function deletePrReviewComment(input) {
659
751
  try {
660
752
  await execFileAsync("gh", [
661
753
  "api",
754
+ ...ghHostnameArgs(input.resolved),
662
755
  "--method",
663
756
  "DELETE",
664
757
  endpoint,
@@ -689,6 +782,7 @@ async function setPrReviewThreadResolved(input) {
689
782
  const { stdout } = await execWithInput("gh", [
690
783
  "api",
691
784
  "graphql",
785
+ ...ghHostnameArgs({ host: input.host }),
692
786
  "--input",
693
787
  "-"
694
788
  ], JSON.stringify({
@@ -719,12 +813,16 @@ function githubCommandError(error, fallback) {
719
813
  return String(error?.stderr || error?.message || fallback).trim().slice(0, 500) || fallback;
720
814
  }
721
815
  async function buildPrSession(ref) {
722
- const meta = await fetchPrMetadataViaGh(parsePrRef(ref, await detectCwdRepo() ?? void 0));
816
+ const cwdRepo = await detectCwdRepo();
817
+ const resolved = parsePrRef(ref, cwdRepo ?? void 0);
818
+ const meta = await fetchPrMetadataViaGh(resolved);
819
+ const host = resolved.host ?? parseGitRemoteUrl(meta.url)?.host ?? cwdRepo?.host;
723
820
  return {
724
821
  ref,
725
822
  owner: meta.owner,
726
823
  repo: meta.repo,
727
824
  pullNumber: meta.number,
825
+ host: isGithubDotCom(host) ? void 0 : host,
728
826
  baseSha: meta.baseSha,
729
827
  headSha: meta.headSha,
730
828
  title: meta.title,
@@ -741,12 +839,7 @@ async function buildPrSession(ref) {
741
839
  }
742
840
  /** Refresh `diff` + `existingComments` + head SHA in an existing session. */
743
841
  async function refreshPrSession(session) {
744
- const meta = await fetchPrMetadataViaGh({
745
- owner: session.owner,
746
- repo: session.repo,
747
- pullNumber: session.pullNumber,
748
- ref: session.ref
749
- });
842
+ const meta = await fetchPrMetadataViaGh(resolvedFromSession(session));
750
843
  return {
751
844
  ...session,
752
845
  baseSha: meta.baseSha,
@@ -763,4 +856,4 @@ async function refreshPrSession(session) {
763
856
  };
764
857
  }
765
858
  //#endregion
766
- export { replyToPrComment as _, detectCwdRepo as a, updatePrReviewComment as b, expandMultiLineComments as c, fetchPrChecks as d, fetchPrMetadataViaGh as f, refreshPrSession as g, readGithubToken as h, deletePrReviewComment as i, fetchExistingCommentsViaGh as l, parsePrRef as m, buildReviewPayload as n, detectGhCli as o, parseGhAuthStatusUser as p, decisionToEvent as r, execWithInput as s, buildPrSession as t, fetchExistingReviewsViaGh as u, setPrReviewThreadResolved as v, submitReview as y };
859
+ export { resolvedFromSession as C, updatePrReviewComment as E, replyToPrComment as S, submitReview as T, parseGhAuthStatusUser as _, detectCwdRepo as a, readGithubToken as b, expandMultiLineComments as c, fetchPrChecks as d, fetchPrMetadataViaGh as f, isGithubDotCom as g, githubApiBase as h, deletePrReviewComment as i, fetchExistingCommentsViaGh as l, ghRepoSelector as m, buildReviewPayload as n, detectGhCli as o, ghHostnameArgs as p, decisionToEvent as r, execWithInput as s, buildPrSession as t, fetchExistingReviewsViaGh as u, parseGitRemoteUrl as v, setPrReviewThreadResolved as w, refreshPrSession as x, parsePrRef as y };
@@ -1,4 +1,4 @@
1
- import { i as buildGitDiffArgs, o as parseDiffOptions, t as startServer } from "./server-CczuXSWo.mjs";
1
+ import { i as buildGitDiffArgs, o as parseDiffOptions, t as startServer } from "./server-CRk40DPV.mjs";
2
2
  import { t as formatComments } from "./comment-format-B5AcGcR-.mjs";
3
3
  import { n as formatPlanReview } from "./plan-format-BQyx--mp.mjs";
4
4
  import { a as removeServerLock, i as readServerLock, n as diffScopeKey, o as removeServerLockIfOwned, r as isLockAlive, s as writeServerLock, t as acquireServerStartupLease } from "./server-lock-uqSdDYeR.mjs";
@@ -2,7 +2,7 @@ import { C as isSafePath, S as revertHunk, _ as getUntrackedFilePathsAsync, a as
2
2
  import { a as searchContent, c as trackSelection, i as searchAll, n as saveSettings, o as searchFiles, r as getSearchStatus, s as searchSymbols, t as loadSettings } from "./settings-BVnzGFL7.mjs";
3
3
  import { t as formatComments } from "./comment-format-B5AcGcR-.mjs";
4
4
  import { n as formatPlanReview, r as sectionTitleForLine, t as extractPlanLines } from "./plan-format-BQyx--mp.mjs";
5
- import { a as detectCwdRepo, b as updatePrReviewComment, g as refreshPrSession, i as deletePrReviewComment, l as fetchExistingCommentsViaGh, m as parsePrRef, t as buildPrSession, u as fetchExistingReviewsViaGh, v as setPrReviewThreadResolved, y as submitReview } from "./github-oCZx5aim.mjs";
5
+ import { C as resolvedFromSession, E as updatePrReviewComment, T as submitReview, a as detectCwdRepo, i as deletePrReviewComment, l as fetchExistingCommentsViaGh, t as buildPrSession, u as fetchExistingReviewsViaGh, w as setPrReviewThreadResolved, x as refreshPrSession, y as parsePrRef } from "./github-C27GDU9I.mjs";
6
6
  import { execFileSync } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { dirname, extname, join, resolve } from "node:path";
@@ -2616,12 +2616,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2616
2616
  });
2617
2617
  });
2618
2618
  const syncExistingPrReviewData = async (session) => {
2619
- const resolved = {
2620
- owner: session.owner,
2621
- repo: session.repo,
2622
- pullNumber: session.pullNumber,
2623
- ref: session.ref
2624
- };
2619
+ const resolved = resolvedFromSession(session);
2625
2620
  let existingReviews = await fetchExistingReviewsViaGh(resolved);
2626
2621
  const optimisticReview = session.submittedReviewId == null ? void 0 : session.existingReviews?.find((review) => review.id === session.submittedReviewId);
2627
2622
  if (optimisticReview && !existingReviews.some((review) => review.id === optimisticReview.id) && session.submittedAt != null && Date.now() - session.submittedAt < 12e4) existingReviews = [optimisticReview, ...existingReviews];
@@ -2677,13 +2672,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2677
2672
  const session = await prStore.get();
2678
2673
  if (!session) return notInPrMode(c);
2679
2674
  try {
2680
- const { fetchPrChecks } = await import("./github-BjZZcSKV.mjs");
2681
- const checks = await fetchPrChecks({
2682
- owner: session.owner,
2683
- repo: session.repo,
2684
- pullNumber: session.pullNumber,
2685
- ref: session.ref
2686
- }, session.headSha);
2675
+ const { fetchPrChecks } = await import("./github-8THGZ7Nu.mjs");
2676
+ const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
2687
2677
  const summary = {
2688
2678
  total: checks.length,
2689
2679
  success: checks.filter((x) => x.state === "success").length,
@@ -2714,14 +2704,9 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2714
2704
  const text = typeof body.body === "string" ? body.body.trim() : "";
2715
2705
  if (!text) return c.json({ error: "body is required" }, 400);
2716
2706
  try {
2717
- const { replyToPrComment } = await import("./github-BjZZcSKV.mjs");
2707
+ const { replyToPrComment } = await import("./github-8THGZ7Nu.mjs");
2718
2708
  const result = await replyToPrComment({
2719
- resolved: {
2720
- owner: session.owner,
2721
- repo: session.repo,
2722
- pullNumber: session.pullNumber,
2723
- ref: session.ref
2724
- },
2709
+ resolved: resolvedFromSession(session),
2725
2710
  inReplyTo: id,
2726
2711
  body: text
2727
2712
  });
@@ -2757,12 +2742,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2757
2742
  const text = typeof request.body === "string" ? request.body.trim() : "";
2758
2743
  if (!text) return c.json({ error: "body is required" }, 400);
2759
2744
  const result = await updatePrReviewComment({
2760
- resolved: {
2761
- owner: session.owner,
2762
- repo: session.repo,
2763
- pullNumber: session.pullNumber,
2764
- ref: session.ref
2765
- },
2745
+ resolved: resolvedFromSession(session),
2766
2746
  commentId: id,
2767
2747
  body: text
2768
2748
  });
@@ -2781,12 +2761,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2781
2761
  const id = Number(c.req.param("id"));
2782
2762
  if (!Number.isFinite(id)) return c.json({ error: "Invalid comment id" }, 400);
2783
2763
  const result = await deletePrReviewComment({
2784
- resolved: {
2785
- owner: session.owner,
2786
- repo: session.repo,
2787
- pullNumber: session.pullNumber,
2788
- ref: session.ref
2789
- },
2764
+ resolved: resolvedFromSession(session),
2790
2765
  commentId: id
2791
2766
  });
2792
2767
  if (!result.ok) return c.json({ error: result.error ?? "Delete failed" }, 502);
@@ -2806,7 +2781,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2806
2781
  if (typeof request.resolved !== "boolean") return c.json({ error: "resolved must be a boolean" }, 400);
2807
2782
  const result = await setPrReviewThreadResolved({
2808
2783
  threadId,
2809
- resolved: request.resolved
2784
+ resolved: request.resolved,
2785
+ host: session.host
2810
2786
  });
2811
2787
  if (!result.ok) return c.json({ error: result.error ?? "Thread update failed" }, 502);
2812
2788
  const next = await syncExistingPrReviewData(session);
@@ -2937,7 +2913,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2937
2913
  if (decision !== "approve" && decision !== "comment" && decision !== "request-changes" && decision !== "draft") return c.json({ error: "decision must be one of: approve, comment, request-changes, draft" }, 400);
2938
2914
  const generalBody = typeof body.body === "string" ? body.body : "";
2939
2915
  if (body.dryRun === true) {
2940
- const { buildReviewPayload } = await import("./github-BjZZcSKV.mjs");
2916
+ const { buildReviewPayload } = await import("./github-8THGZ7Nu.mjs");
2941
2917
  const payload = buildReviewPayload({
2942
2918
  decision,
2943
2919
  body: generalBody,
@@ -2952,12 +2928,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2952
2928
  });
2953
2929
  }
2954
2930
  const result = await submitReview({
2955
- resolved: {
2956
- owner: session.owner,
2957
- repo: session.repo,
2958
- pullNumber: session.pullNumber,
2959
- ref: session.ref
2960
- },
2931
+ resolved: resolvedFromSession(session),
2961
2932
  decision,
2962
2933
  body: generalBody,
2963
2934
  comments: session.comments ?? []
@@ -2966,12 +2937,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2966
2937
  let existingComments = session.existingComments;
2967
2938
  let existingReviews = session.existingReviews ?? [];
2968
2939
  try {
2969
- const resolved = {
2970
- owner: session.owner,
2971
- repo: session.repo,
2972
- pullNumber: session.pullNumber,
2973
- ref: session.ref
2974
- };
2940
+ const resolved = resolvedFromSession(session);
2975
2941
  existingReviews = await fetchExistingReviewsViaGh(resolved);
2976
2942
  existingComments = await fetchExistingCommentsViaGh(resolved, existingReviews);
2977
2943
  } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffing",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "local-first CLI for reviewing, navigating, and discussing git diffs with AI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,2 +0,0 @@
1
- import { _ as replyToPrComment, d as fetchPrChecks, n as buildReviewPayload } from "./github-oCZx5aim.mjs";
2
- export { buildReviewPayload, fetchPrChecks, replyToPrComment };