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 +33 -9
- package/dist/{cli-agent-CLrK54k2.mjs → cli-agent-7DUbPdeW.mjs} +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/{doctor-CmP0u7-d.mjs → doctor-DRWEgsMs.mjs} +1 -1
- package/dist/github-8THGZ7Nu.mjs +2 -0
- package/dist/{github-oCZx5aim.mjs → github-C27GDU9I.mjs} +121 -28
- package/dist/{mcp-CqxXILYL.mjs → mcp-D4cwru99.mjs} +1 -1
- package/dist/{server-CczuXSWo.mjs → server-CRk40DPV.mjs} +13 -47
- package/package.json +1 -1
- package/dist/github-BjZZcSKV.mjs +0 -2
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?
|
|
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.
|
|
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.
|
|
196
|
-
- **
|
|
197
|
-
- **
|
|
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
|
|
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
|
|
219
|
-
pnpm build: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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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";
|
|
@@ -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?:\/\/
|
|
173
|
+
const urlMatch = /^https?:\/\/([^/]+)\/([^/]+)\/([^/]+)\/pull\/(\d+)/i.exec(trimmed);
|
|
110
174
|
if (urlMatch) return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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,
|
|
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-
|
|
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 {
|
|
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-
|
|
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-
|
|
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-
|
|
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
package/dist/github-BjZZcSKV.mjs
DELETED