@dev.fast/whiteboard 0.2.5-preview.20261013.111 → 0.2.5-preview.20261013.113

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.
@@ -23,7 +23,7 @@ async function runReviewAgentCli(input) {
23
23
  [REVIEW_AGENT_HEADER]: reviewSessionAgent(env)
24
24
  };
25
25
  if (mode === "mcp") {
26
- const { serveReviewMcp } = await import("./mcp-t7VN9R3k.js");
26
+ const { serveReviewMcp } = await import("./mcp-B8kuRSXM.js");
27
27
  await serveReviewMcp(async (key, agentKind) => {
28
28
  const connected = await connectReviewInstance(key ? {
29
29
  ...env,
@@ -0,0 +1,2 @@
1
+ import { n as runReviewAgentCli } from "./agent-cli-04xbDj23.js";
2
+ export { runReviewAgentCli };
@@ -1,5 +1,5 @@
1
1
  import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
- import { g as activityUpdateSchema, h as activityEndSchema, i as commandSchema, m as activityBeginSchema, n as uploadSchema } from "./local-data-BlERtVm2.js";
2
+ import { g as activityUpdateSchema, h as activityEndSchema, i as commandSchema, m as activityBeginSchema, n as uploadSchema } from "./local-data-Dy1pNtLF.js";
3
3
  import { x as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-u4L_1VZa.js";
4
4
  import { t as findReviewPackageRoot } from "./package-paths-B9PDoAl6.js";
5
5
  import { z } from "zod";
@@ -1 +1 @@
1
- {"version":"0.2.5-preview.20261013.111","commit":"6247009eecc0d81c1b98268ea2e875074d3a9803","dirty":true,"builtAt":"2026-10-13T05:42:27.992Z"}
1
+ {"version":"0.2.5-preview.20261013.113","commit":"c9ba2f7bc95523c617b751c760f4cb2a5fb0c5c3","dirty":true,"builtAt":"2026-10-13T14:45:33.958Z"}
@@ -12,7 +12,7 @@ import { t as ReviewTelemetry } from "./review-telemetry-CmM6tCbj.js";
12
12
  import { a as isOwnedShim, f as windowsInstallerCommand, m as connectPrompts, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-D0hF0sKk.js";
13
13
  import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
14
14
  import { n as readReviewServerDiscovery, o as reviewServerStateDir, r as readReviewServerHealth, s as serverNotReady } from "./server-discovery-ZVi8Ay8J.js";
15
- import { t as reviewAgentCliHelp } from "./agent-cli-BBPxwUOd.js";
15
+ import { t as reviewAgentCliHelp } from "./agent-cli-04xbDj23.js";
16
16
  import { closeSync, existsSync, fstatSync, mkdirSync, mkdtempSync, openSync, readSync, renameSync, writeFileSync } from "node:fs";
17
17
  import { realpath } from "node:fs/promises";
18
18
  import path from "node:path";
@@ -4494,7 +4494,7 @@ async function runReviewCli(input) {
4494
4494
  process.once("SIGINT", stop);
4495
4495
  process.once("SIGTERM", stop);
4496
4496
  try {
4497
- const { runHeadlessServer } = await import("./headless-host-BHD_Vg5F.js");
4497
+ const { runHeadlessServer } = await import("./headless-host-BLLJ7Dyu.js");
4498
4498
  await runHeadlessServer({
4499
4499
  stateDir,
4500
4500
  port,
@@ -4545,7 +4545,7 @@ async function runReviewCli(input) {
4545
4545
  if (problem) throw new Error(`Cannot tell whether a Whiteboard Desktop is using ${stateDir}: ${problem.message}`);
4546
4546
  const inUse = () => /* @__PURE__ */ new Error(`A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`);
4547
4547
  if (desktops.instances.some(({ discovery }) => [discovery.appPid, discovery.serverPid].some((pid) => processIsAlive(pid)))) throw inUse();
4548
- const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-j97aRTS7.js"), import("./headless-host-BHD_Vg5F.js")]);
4548
+ const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-aMSw1oCM.js"), import("./headless-host-BLLJ7Dyu.js")]);
4549
4549
  const reset = await withHeadlessServerLock(await realpath(stateDir), async () => {
4550
4550
  const local = await openReviewProfile(stateDir, { manageWorkspaces: false });
4551
4551
  try {
@@ -4773,7 +4773,7 @@ async function runReviewCli(input) {
4773
4773
  return commands;
4774
4774
  } });
4775
4775
  for (const [name, description] of [["api", "Call a JSON review authoring tool on the running server"], ["mcp", "Serve the JSON review authoring tools over stdio MCP"]]) configureOutput(program.command(name).description(description).argument("[args...]", "tool name and JSON input").allowUnknownOption().allowExcessArguments().helpOption(false).passThroughOptions().addHelpText("after", `\n${reviewAgentCliHelp}`), "plain").action(async (args) => {
4776
- const { runReviewAgentCli } = await import("./agent-cli-YSuhE5X8.js");
4776
+ const { runReviewAgentCli } = await import("./agent-cli-BR_Jw5BL.js");
4777
4777
  state.exitCode = await runReviewAgentCli({
4778
4778
  ...input,
4779
4779
  env: authoringEnv(),
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ async function runCli(effectivePath) {
17
17
  `);
18
18
  return 1;
19
19
  }
20
- const { runReviewCli } = await import("./cli-runner-C1gY-HAI.js");
20
+ const { runReviewCli } = await import("./cli-runner-C3men22A.js");
21
21
  return runReviewCli({
22
22
  argv,
23
23
  cliPaths: {
@@ -1,8 +1,8 @@
1
1
  import { p as isObjectValue } from "./src-X9phtB2j.js";
2
2
  import "./review-home-paths-Y9mYWkCI.js";
3
3
  import { O as withFileLock, P as writePrivateJsonAtomic } from "./s3-config-CAxhO9u_.js";
4
- import { t as openReviewProfile } from "./profile-BsYDBr7t.js";
5
- import { i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, p as GlobalReviewDesktopVerbRelay, t as drainServerCrashReport } from "./process-error-telemetry-C0swByfp.js";
4
+ import { t as openReviewProfile } from "./profile-tbGWuWHu.js";
5
+ import { i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, p as GlobalReviewDesktopVerbRelay, t as drainServerCrashReport } from "./process-error-telemetry-BCBYp2t4.js";
6
6
  import { i as reviewServerDiscoveryPath, t as headlessServerLockPath } from "./server-discovery-ZVi8Ay8J.js";
7
7
  import { randomBytes, randomUUID } from "node:crypto";
8
8
  import { mkdir, realpath, rm } from "node:fs/promises";
@@ -1231,8 +1231,6 @@ const mapInputSchema = z.strictObject({
1231
1231
  });
1232
1232
  //#endregion
1233
1233
  //#region src/review-api/pull-request.ts
1234
- const GH_TIMEOUT_MS = 2e4;
1235
- const API_TIMEOUT_MS = 15e3;
1236
1234
  const FETCH_TIMEOUT_MS = 12e4;
1237
1235
  const LOCAL_TIMEOUT_MS = 3e4;
1238
1236
  const defaultPullRequestDeps = {
@@ -1255,82 +1253,9 @@ const defaultPullRequestDeps = {
1255
1253
  }),
1256
1254
  fetch: (input, init) => fetch(input, init)
1257
1255
  };
1258
- /** Host, owner/repo and number from a canonical PR URL (validated by the command schema). */
1259
- function pullRequestAddress(url) {
1260
- const { hostname, pathname } = new URL(url);
1261
- const [, owner, repo, , number] = pathname.split("/");
1262
- return {
1263
- host: hostname,
1264
- slug: `${owner}/${repo}`,
1265
- number: Number(number)
1266
- };
1267
- }
1268
- const metadataSchema = z.object({
1269
- number: z.number().int().positive(),
1270
- title: z.string(),
1271
- baseRefName: z.string().min(1),
1272
- baseRefOid: z.string().optional()
1273
- });
1274
- /** gh first (it carries the user's auth), then, for github.com only, GitHub's
1275
- * public REST API: Enterprise hosts require gh. */
1276
- async function readPullRequest(url, deps) {
1277
- const { host, slug, number } = pullRequestAddress(url);
1278
- let ghFailure;
1279
- try {
1280
- const stdout = await deps.run("gh", [
1281
- "pr",
1282
- "view",
1283
- String(number),
1284
- "--repo",
1285
- `${host}/${slug}`,
1286
- "--json",
1287
- "number,title,baseRefName,baseRefOid"
1288
- ], { timeoutMs: GH_TIMEOUT_MS });
1289
- return {
1290
- host,
1291
- slug,
1292
- ...metadataSchema.parse(JSON.parse(stdout))
1293
- };
1294
- } catch (error) {
1295
- ghFailure = firstLine(errorMessage(error));
1296
- }
1297
- if (host !== "github.com") throw new ReviewInputError(`Could not read ${url}: gh failed (${ghFailure}). Run \`gh auth login --hostname ${host}\` with an account that can read the repository, then retry.`, 409);
1298
- let response;
1299
- try {
1300
- response = await deps.fetch(`https://api.github.com/repos/${slug}/pulls/${number}`, {
1301
- headers: {
1302
- Accept: "application/vnd.github+json",
1303
- "X-GitHub-Api-Version": "2022-11-28"
1304
- },
1305
- signal: AbortSignal.timeout(API_TIMEOUT_MS)
1306
- });
1307
- } catch (error) {
1308
- throw unreadable(url, ghFailure, `the GitHub API is unreachable (${firstLine(errorMessage(error))})`);
1309
- }
1310
- if (response.status === 404) throw new ReviewInputError(`${url} was not found: it does not exist, or it is private and gh is not signed in to an account that can read it (run \`gh auth status\`). gh said: ${ghFailure}`, 404);
1311
- if (response.status === 403 || response.status === 429) throw unreadable(url, ghFailure, response.headers.get("x-ratelimit-remaining") === "0" || response.status === 429 ? "the unauthenticated GitHub API rate limit is exhausted" : "the GitHub API refused the request");
1312
- if (!response.ok) throw unreadable(url, ghFailure, `the GitHub API returned HTTP ${response.status}`);
1313
- const parsed = z.object({
1314
- number: z.number(),
1315
- title: z.string(),
1316
- base: z.object({
1317
- ref: z.string().min(1),
1318
- sha: z.string()
1319
- })
1320
- }).safeParse(await response.json().catch(() => void 0));
1321
- if (!parsed.success) throw unreadable(url, ghFailure, "the GitHub API response was malformed");
1322
- return {
1323
- host,
1324
- slug,
1325
- number: parsed.data.number,
1326
- title: parsed.data.title,
1327
- baseRefName: parsed.data.base.ref,
1328
- baseRefOid: parsed.data.base.sha
1329
- };
1330
- }
1331
- /** Remotes by the host and owner/repo their configured URL names. The
1332
- * configured URL, not the insteadOf rewrite: a mirror still names its repo. */
1333
- async function githubRemotes(gitDir, deps) {
1256
+ /** Remotes by their configured URL, not the insteadOf rewrite: a mirror
1257
+ * still names its repo. */
1258
+ async function remoteUrls(gitDir, deps) {
1334
1259
  return (await deps.run("git", [
1335
1260
  "--git-dir",
1336
1261
  gitDir,
@@ -1339,17 +1264,15 @@ async function githubRemotes(gitDir, deps) {
1339
1264
  String.raw`^remote\..*\.url$`
1340
1265
  ], { timeoutMs: LOCAL_TIMEOUT_MS }).catch(() => "")).split("\n").flatMap((line) => {
1341
1266
  const match = /^remote\.(.+)\.url\s+(\S+)/.exec(line.trim());
1342
- const remote = match && parseGitRemote(match[2]);
1343
- return match && remote ? [{
1267
+ return match ? [{
1344
1268
  name: match[1],
1345
- host: remote.host,
1346
- slug: remote.slug
1269
+ url: match[2]
1347
1270
  }] : [];
1348
1271
  });
1349
1272
  }
1350
1273
  /** Refs Review owns for one PR; the user's branches and bookmarks never move. */
1351
- function pullRequestRefs(pr) {
1352
- const prefix = `refs/review/github/${`${pr.host === "github.com" ? "" : `${pr.host}/`}${pr.slug}`.toLowerCase()}/pull/${pr.number}`;
1274
+ function pullRequestRefs(provider, pr) {
1275
+ const prefix = `refs/review/${provider}/${`${pr.host === "github.com" ? "" : `${pr.host}/`}${pr.slug}`.toLowerCase()}/pull/${pr.number}`;
1353
1276
  return {
1354
1277
  head: `${prefix}/head`,
1355
1278
  base: `${prefix}/base`,
@@ -1369,7 +1292,7 @@ function pullRequestRefs(pr) {
1369
1292
  */
1370
1293
  async function fetchPullRequest(input, deps) {
1371
1294
  const { pullRequest: pr } = input;
1372
- const refs = pullRequestRefs(pr);
1295
+ const refs = pullRequestRefs(input.provider, pr);
1373
1296
  const git = (args, timeoutMs = LOCAL_TIMEOUT_MS) => deps.run("git", [
1374
1297
  "--git-dir",
1375
1298
  input.gitDir,
@@ -1401,11 +1324,11 @@ async function fetchPullRequest(input, deps) {
1401
1324
  };
1402
1325
  let baseTip;
1403
1326
  try {
1404
- await fetchRefs(`+refs/pull/${pr.number}/head:${refs.head}`, `+refs/heads/${pr.baseRefName}:${refs.base}`);
1327
+ await fetchRefs(`+${pr.headRef}:${refs.head}`, `+refs/heads/${pr.baseRefName}:${refs.base}`);
1405
1328
  baseTip = await commit(refs.base);
1406
1329
  } catch (error) {
1407
1330
  try {
1408
- await fetchRefs(`+refs/pull/${pr.number}/head:${refs.head}`);
1331
+ await fetchRefs(`+${pr.headRef}:${refs.head}`);
1409
1332
  } catch {
1410
1333
  throw new ReviewInputError(`Could not fetch PR #${pr.number} from remote "${input.remote}" (${pr.slug}). Check \`git fetch ${input.remote}\` works with your Git credentials, then retry. Git said: ${firstLine(errorMessage(error))}`, 409);
1411
1334
  }
@@ -1460,11 +1383,115 @@ async function indexForJj(rootPath, commits, git, deps) {
1460
1383
  await jjImport().catch(() => void 0);
1461
1384
  }
1462
1385
  }
1386
+ function firstLine(text) {
1387
+ return text.split("\n")[0].slice(0, 300);
1388
+ }
1389
+ //#endregion
1390
+ //#region src/review-api/github-provider.ts
1391
+ const GH_TIMEOUT_MS = 2e4;
1392
+ const API_TIMEOUT_MS = 15e3;
1393
+ const GITHUB_PR_REGEX = /^https:\/\/([a-z0-9.-]+)\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9]\d*)$/;
1394
+ const metadataSchema = z.object({
1395
+ number: z.number().int().positive(),
1396
+ title: z.string(),
1397
+ baseRefName: z.string().min(1),
1398
+ baseRefOid: z.string().optional()
1399
+ });
1463
1400
  function unreadable(url, ghFailure, fallback) {
1464
1401
  return new ReviewInputError(`Could not read ${url}: gh failed (${ghFailure}) and ${fallback}. Run \`gh auth status\` and sign in with an account that can read the repository, then retry.`, 409);
1465
1402
  }
1466
- function firstLine(text) {
1467
- return text.split("\n")[0].slice(0, 300);
1403
+ //#endregion
1404
+ //#region src/review-api/provider.ts
1405
+ const providers = [{
1406
+ id: "github",
1407
+ parseUrl(url) {
1408
+ const match = GITHUB_PR_REGEX.exec(url);
1409
+ const number = Number(match?.[4]);
1410
+ if (!match || !Number.isSafeInteger(number)) return void 0;
1411
+ return {
1412
+ host: match[1],
1413
+ slug: `${match[2]}/${match[3]}`,
1414
+ number
1415
+ };
1416
+ },
1417
+ /** gh first (it carries the user's auth), then, for github.com only, GitHub's
1418
+ * public REST API: Enterprise hosts require gh. */
1419
+ async readPullRequest({ host, slug, number }, deps) {
1420
+ const url = `https://${host}/${slug}/pull/${number}`;
1421
+ const headRef = `refs/pull/${number}/head`;
1422
+ let ghFailure;
1423
+ try {
1424
+ const stdout = await deps.run("gh", [
1425
+ "pr",
1426
+ "view",
1427
+ String(number),
1428
+ "--repo",
1429
+ `${host}/${slug}`,
1430
+ "--json",
1431
+ "number,title,baseRefName,baseRefOid"
1432
+ ], { timeoutMs: GH_TIMEOUT_MS });
1433
+ return {
1434
+ host,
1435
+ slug,
1436
+ headRef,
1437
+ ...metadataSchema.parse(JSON.parse(stdout))
1438
+ };
1439
+ } catch (error) {
1440
+ ghFailure = firstLine(errorMessage(error));
1441
+ }
1442
+ if (host !== "github.com") throw new ReviewInputError(`Could not read ${url}: gh failed (${ghFailure}). Run \`gh auth login --hostname ${host}\` with an account that can read the repository, then retry.`, 409);
1443
+ let response;
1444
+ try {
1445
+ response = await deps.fetch(`https://api.github.com/repos/${slug}/pulls/${number}`, {
1446
+ headers: {
1447
+ Accept: "application/vnd.github+json",
1448
+ "X-GitHub-Api-Version": "2022-11-28"
1449
+ },
1450
+ signal: AbortSignal.timeout(API_TIMEOUT_MS)
1451
+ });
1452
+ } catch (error) {
1453
+ throw unreadable(url, ghFailure, `the GitHub API is unreachable (${firstLine(errorMessage(error))})`);
1454
+ }
1455
+ if (response.status === 404) throw new ReviewInputError(`${url} was not found: it does not exist, or it is private and gh is not signed in to an account that can read it (run \`gh auth status\`). gh said: ${ghFailure}`, 404);
1456
+ if (response.status === 403 || response.status === 429) throw unreadable(url, ghFailure, response.headers.get("x-ratelimit-remaining") === "0" || response.status === 429 ? "the unauthenticated GitHub API rate limit is exhausted" : "the GitHub API refused the request");
1457
+ if (!response.ok) throw unreadable(url, ghFailure, `the GitHub API returned HTTP ${response.status}`);
1458
+ const parsed = z.object({
1459
+ number: z.number(),
1460
+ title: z.string(),
1461
+ base: z.object({
1462
+ ref: z.string().min(1),
1463
+ sha: z.string()
1464
+ })
1465
+ }).safeParse(await response.json().catch(() => void 0));
1466
+ if (!parsed.success) throw unreadable(url, ghFailure, "the GitHub API response was malformed");
1467
+ return {
1468
+ host,
1469
+ slug,
1470
+ headRef,
1471
+ number: parsed.data.number,
1472
+ title: parsed.data.title,
1473
+ baseRefName: parsed.data.base.ref,
1474
+ baseRefOid: parsed.data.base.sha
1475
+ };
1476
+ },
1477
+ matchesRemote(remoteUrl, address) {
1478
+ const remote = parseGitRemote(remoteUrl);
1479
+ return remote?.host === address.host && remote.slug.toLowerCase() === address.slug.toLowerCase();
1480
+ }
1481
+ }];
1482
+ function pullRequestProvider(url) {
1483
+ for (const provider of providers) {
1484
+ const address = provider.parseUrl(url);
1485
+ if (address) return {
1486
+ provider,
1487
+ address
1488
+ };
1489
+ }
1490
+ }
1491
+ function resolvePullRequest(url) {
1492
+ const resolution = pullRequestProvider(url);
1493
+ if (!resolution) throw new ReviewInputError(`Unsupported pull request URL: ${url}`, 400);
1494
+ return resolution;
1468
1495
  }
1469
1496
  //#endregion
1470
1497
  //#region src/review-api/activity.ts
@@ -2153,7 +2180,7 @@ var AskHistory = class {
2153
2180
  };
2154
2181
  //#endregion
2155
2182
  //#region src/review-api/origin.ts
2156
- const pullRequestUrl = z.string().regex(/^https:\/\/[a-z0-9.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9]\d*$/, "Use a canonical GitHub PR URL: https://github.com/owner/repository/pull/123, or the same path on a GitHub Enterprise host.").refine((url) => Number.isSafeInteger(Number(url.split("/").at(-1))), "PR number is too large.");
2183
+ const pullRequestUrl = z.string().refine((url) => pullRequestProvider(url) !== void 0, "Use a canonical GitHub PR URL: https://github.com/owner/repository/pull/123, or the same path on a GitHub Enterprise host.");
2157
2184
  /** One key per PR: GitHub owner and repository names are case-insensitive,
2158
2185
  * and the URL pattern admits only ASCII, so lowercasing is canonical. */
2159
2186
  function pullRequestKey(url) {
@@ -4215,11 +4242,12 @@ var LocalReviewData = class {
4215
4242
  /** The PR's current comparison, fetched into a registered checkout of its repository. */
4216
4243
  async resolvePullRequest(url, repository) {
4217
4244
  const deps = this.options.pullRequests ?? defaultPullRequestDeps;
4218
- const { host, slug, number } = pullRequestAddress(url);
4219
- const checkout = await this.pullRequestCheckout(host, slug, repository, deps);
4220
- const pullRequest = await readPullRequest(url, deps);
4245
+ const { provider, address } = resolvePullRequest(url);
4246
+ const checkout = await this.pullRequestCheckout(provider, address, repository, deps);
4247
+ const pullRequest = await provider.readPullRequest(address, deps);
4221
4248
  const { head, base } = await fetchPullRequest({
4222
4249
  ...checkout,
4250
+ provider: provider.id,
4223
4251
  pullRequest
4224
4252
  }, deps);
4225
4253
  const repositoryId = checkout.repositoryId;
@@ -4235,11 +4263,11 @@ var LocalReviewData = class {
4235
4263
  base,
4236
4264
  head
4237
4265
  },
4238
- title: pullRequest.title.trim() || `PR #${number}`
4266
+ title: pullRequest.title.trim() || `PR #${address.number}`
4239
4267
  };
4240
4268
  }
4241
4269
  /** A registered checkout with a remote for host/owner/repo, and that remote. */
4242
- async pullRequestCheckout(host, slug, repository, deps) {
4270
+ async pullRequestCheckout(provider, address, repository, deps) {
4243
4271
  const registered = this.store.repositories();
4244
4272
  const candidates = repository.id ? registered.filter((entry) => entry.id === repository.id) : [...registered.filter((entry) => entry.id === repository.preferred), ...registered.filter((entry) => entry.id !== repository.preferred)];
4245
4273
  if (repository.id && candidates.length === 0) throw new ReviewInputError("Repository is not registered.", 404);
@@ -4248,7 +4276,7 @@ var LocalReviewData = class {
4248
4276
  const vcs = await this.vcs(id);
4249
4277
  const gitDir = vcs && await gitCommonDir(vcs.rootPath);
4250
4278
  if (!vcs || !gitDir) continue;
4251
- const remote = (await githubRemotes(gitDir, deps)).find((entry) => entry.host === host && entry.slug.toLowerCase() === slug.toLowerCase());
4279
+ const remote = (await remoteUrls(gitDir, deps)).find((entry) => provider.matchesRemote(entry.url, address));
4252
4280
  if (remote) return {
4253
4281
  repositoryId: id,
4254
4282
  rootPath: vcs.rootPath,
@@ -4257,6 +4285,7 @@ var LocalReviewData = class {
4257
4285
  remote: remote.name
4258
4286
  };
4259
4287
  }
4288
+ const { host, slug } = address;
4260
4289
  const name = host === "github.com" ? slug : `${host}/${slug}`;
4261
4290
  throw new ReviewInputError(repository.id ? `That checkout has no GitHub remote for ${name}. Add one, or omit repositoryId.` : `No registered checkout has a GitHub remote for ${name}. Register a checkout of ${name} with review_register_repository first, or pass a target.`, 404);
4262
4291
  }
@@ -1,6 +1,6 @@
1
1
  import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
2
  import { a as toolResultText, s as ReviewApiError } from "./agent-client-P3J4y8Zn.js";
3
- import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-D8WXsm_g.js";
3
+ import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-fdA0RPIT.js";
4
4
  import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-CJHBCHMh.js";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -1,7 +1,7 @@
1
1
  import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
2
  import { c as jsonString, m as isStringValue, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
3
  import { a as sharedReviewId, c as verifyShareRepository, d as StreamLimitError, f as readBoundedStream, l as exportShare, m as shareIdSchema, n as ShareClient, p as parseShareLink, r as SharePreflightError, s as readShareRepository, t as ShareAuthError, u as validateShareSources } from "./client-DzcSt6pV.js";
4
- import { _ as invalidateStructuralComparisons, a as inspectSnapshot, c as askAgentIds, d as askQuestionSchema, f as AgentSelectionSchema, i as commandSchema, l as askChoiceKinds, p as selectionMarkdown, r as SCRATCHPAD_ID, s as applyAskChange, u as askPicksSchema, v as diffrExecutable, y as diffrMissingError } from "./local-data-BlERtVm2.js";
4
+ import { _ as invalidateStructuralComparisons, a as inspectSnapshot, c as askAgentIds, d as askQuestionSchema, f as AgentSelectionSchema, i as commandSchema, l as askChoiceKinds, p as selectionMarkdown, r as SCRATCHPAD_ID, s as applyAskChange, u as askPicksSchema, v as diffrExecutable, y as diffrMissingError } from "./local-data-Dy1pNtLF.js";
5
5
  import { d as REVIEW_CLIENT_REMOTE, i as parseReviewVerbRequest, r as parseReviewDesktopVerbResult, u as REVIEW_CLIENT_HEADER, v as reviewDiffrSummarizerInputSchema, y as decodeStructuralDiffEvent } from "./src-WCbabvdS.js";
6
6
  import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as openUrlInBrowser, it as clearStoreAuth, ot as readStoreAuth, rt as DEFAULT_STORE_ORIGIN, st as runStoreLogin, z as selectTraceStorage } from "./review-home-paths-Y9mYWkCI.js";
7
7
  import { $ as unionIntervals, G as selectionKey, H as comparisonKey, I as fileLineRangeSchema, J as coverageSources, N as lensSelections, Q as subtractIntervals, T as selectionReferences, U as formatAnchor, V as anchorSelection, W as resolveDiffSelection, Y as emptyCoverage, Z as scopedCoverage, h as elements, k as UNCATEGORIZED_LENS_ID, u as anchorPins } from "./stored-document-migration-u4L_1VZa.js";
@@ -9,7 +9,7 @@ import { A as devReviewHome, h as TraceConfigurationError, i as isS3MockMode } f
9
9
  import { t as errorMessage } from "./error-message-OtiDonty.js";
10
10
  import { i as readReviewPackageVersion, n as readBuildCommit } from "./package-paths-B9PDoAl6.js";
11
11
  import { a as REVIEW_APP_SESSION_ID_HEADER, c as sanitizeUiTelemetryEvent, i as BUNDLE_FRAME_PATTERN, l as cleanTelemetryText, r as reviewRequestOrigin, s as isReportableCleanedMessage } from "./request-origin-DA4K1qWY.js";
12
- import { a as scratchpadAvailable, c as readQuerySchemas, i as renderInstructions, o as inspectQuerySchema, r as instructionsQuerySchema, s as queryAnchor, t as authoringTools } from "./authoring-tools-D8WXsm_g.js";
12
+ import { a as scratchpadAvailable, c as readQuerySchemas, i as renderInstructions, o as inspectQuerySchema, r as instructionsQuerySchema, s as queryAnchor, t as authoringTools } from "./authoring-tools-fdA0RPIT.js";
13
13
  import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
14
14
  import { createRequire } from "node:module";
15
15
  import crypto$1, { createHash, randomUUID } from "node:crypto";
@@ -0,0 +1,2 @@
1
+ import { t as openReviewProfile } from "./profile-tbGWuWHu.js";
2
+ export { openReviewProfile };
@@ -1,4 +1,4 @@
1
- import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-BlERtVm2.js";
1
+ import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-Dy1pNtLF.js";
2
2
  import "./review-home-paths-Y9mYWkCI.js";
3
3
  import { O as withFileLock } from "./s3-config-CAxhO9u_.js";
4
4
  import { access, mkdir } from "node:fs/promises";
@@ -8,12 +8,12 @@ import { C as resourceReferences, M as lensSchema, p as documentSchema } from ".
8
8
  import { A as devReviewHome, P as writePrivateJsonAtomic } from "../s3-config-CAxhO9u_.js";
9
9
  import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "../package-paths-B9PDoAl6.js";
10
10
  import { c as reviewInstanceIdentity, t as REVIEW_INSTANCE_ENV } from "../desktop-discovery-Ba3dD5Cn.js";
11
- import { t as openReviewProfile } from "../profile-BsYDBr7t.js";
11
+ import { t as openReviewProfile } from "../profile-tbGWuWHu.js";
12
12
  import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-CmM6tCbj.js";
13
13
  import { o as REVIEW_TELEMETRY_TABS } from "../request-origin-DA4K1qWY.js";
14
14
  import { c as removeLegacyReviewSkills, d as skipCliInstall, i as finishCliInstallUpdate, l as resetCliInstall, n as applyCliInstall, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-D0hF0sKk.js";
15
- import { _ as setDiffrConfigValue, a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, g as saveDiffrSummarizer, h as readDiffrConfig, i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson, v as testDiffrSummarizer } from "../process-error-telemetry-C0swByfp.js";
16
- import { c as readQuerySchemas } from "../authoring-tools-D8WXsm_g.js";
15
+ import { _ as setDiffrConfigValue, a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, g as saveDiffrSummarizer, h as readDiffrConfig, i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson, v as testDiffrSummarizer } from "../process-error-telemetry-BCBYp2t4.js";
16
+ import { c as readQuerySchemas } from "../authoring-tools-fdA0RPIT.js";
17
17
  import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
18
18
  import { z } from "zod";
19
19
  import fs, { existsSync } from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.2.5-preview.20261013.111",
3
+ "version": "0.2.5-preview.20261013.113",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -73,10 +73,10 @@
73
73
  "typescript": "6.0.3",
74
74
  "vitest": "4.1.11",
75
75
  "@dev.fast/review-protocol": "0.0.0",
76
- "@dev.fast/trace-core": "0.0.0",
76
+ "@dev.fast/json": "0.0.0",
77
77
  "@dev.fast/review-share-protocol": "0.1.2",
78
78
  "@dev.fast/trace-protocol": "0.5.0",
79
- "@dev.fast/json": "0.0.0"
79
+ "@dev.fast/trace-core": "0.0.0"
80
80
  },
81
81
  "engines": {
82
82
  "node": ">=24 <25"
@@ -86,7 +86,7 @@
86
86
  "repo": "devdotfast/review",
87
87
  "docs": "packages/review/instructions/authoring.md"
88
88
  },
89
- "gitHead": "6247009eecc0d81c1b98268ea2e875074d3a9803",
89
+ "gitHead": "c9ba2f7bc95523c617b751c760f4cb2a5fb0c5c3",
90
90
  "scripts": {
91
91
  "prebuild": "pnpm run build:workspace-deps",
92
92
  "build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
@@ -0,0 +1,154 @@
1
+ import { parseGitRemote } from "@dev.fast/local-vcs";
2
+ import { errorMessage } from "@dev.fast/trace-core";
3
+ import { z } from "zod";
4
+
5
+ import { ReviewInputError } from "./document.js";
6
+ import type { PullRequestProvider } from "./provider.js";
7
+ import { firstLine } from "./pull-request.js";
8
+
9
+ const GH_TIMEOUT_MS = 20_000;
10
+
11
+ const API_TIMEOUT_MS = 15_000;
12
+
13
+ const GITHUB_PR_REGEX =
14
+ /^https:\/\/([a-z0-9.-]+)\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9]\d*)$/;
15
+
16
+ const metadataSchema = z.object({
17
+ number: z.number().int().positive(),
18
+ title: z.string(),
19
+ baseRefName: z.string().min(1),
20
+ baseRefOid: z.string().optional(),
21
+ });
22
+
23
+ function unreadable(url: string, ghFailure: string, fallback: string) {
24
+ return new ReviewInputError(
25
+ `Could not read ${url}: gh failed (${ghFailure}) and ${fallback}. Run \`gh auth status\` and sign in with an account that can read the repository, then retry.`,
26
+ 409,
27
+ );
28
+ }
29
+
30
+ export const githubProvider: PullRequestProvider = {
31
+ id: "github",
32
+
33
+ parseUrl(url) {
34
+ const match = GITHUB_PR_REGEX.exec(url);
35
+ const number = Number(match?.[4]);
36
+
37
+ if (!match || !Number.isSafeInteger(number)) return undefined;
38
+
39
+ return { host: match[1]!, slug: `${match[2]}/${match[3]}`, number };
40
+ },
41
+
42
+ /** gh first (it carries the user's auth), then, for github.com only, GitHub's
43
+ * public REST API: Enterprise hosts require gh. */
44
+ async readPullRequest({ host, slug, number }, deps) {
45
+ const url = `https://${host}/${slug}/pull/${number}`;
46
+ const headRef = `refs/pull/${number}/head`;
47
+ let ghFailure: string;
48
+
49
+ try {
50
+ const stdout = await deps.run(
51
+ "gh",
52
+ [
53
+ "pr",
54
+ "view",
55
+ String(number),
56
+ "--repo",
57
+ `${host}/${slug}`,
58
+ "--json",
59
+ "number,title,baseRefName,baseRefOid",
60
+ ],
61
+ { timeoutMs: GH_TIMEOUT_MS },
62
+ );
63
+
64
+ return {
65
+ host,
66
+ slug,
67
+ headRef,
68
+ ...metadataSchema.parse(JSON.parse(stdout)),
69
+ };
70
+ } catch (error) {
71
+ ghFailure = firstLine(errorMessage(error));
72
+ }
73
+
74
+ if (host !== "github.com")
75
+ throw new ReviewInputError(
76
+ `Could not read ${url}: gh failed (${ghFailure}). Run \`gh auth login --hostname ${host}\` with an account that can read the repository, then retry.`,
77
+ 409,
78
+ );
79
+
80
+ let response: Response;
81
+
82
+ try {
83
+ response = await deps.fetch(
84
+ `https://api.github.com/repos/${slug}/pulls/${number}`,
85
+ {
86
+ headers: {
87
+ Accept: "application/vnd.github+json",
88
+ "X-GitHub-Api-Version": "2022-11-28",
89
+ },
90
+ signal: AbortSignal.timeout(API_TIMEOUT_MS),
91
+ },
92
+ );
93
+ } catch (error) {
94
+ throw unreadable(
95
+ url,
96
+ ghFailure,
97
+ `the GitHub API is unreachable (${firstLine(errorMessage(error))})`,
98
+ );
99
+ }
100
+
101
+ if (response.status === 404)
102
+ throw new ReviewInputError(
103
+ `${url} was not found: it does not exist, or it is private and gh is not signed in to an account that can read it (run \`gh auth status\`). gh said: ${ghFailure}`,
104
+ 404,
105
+ );
106
+
107
+ if (response.status === 403 || response.status === 429)
108
+ throw unreadable(
109
+ url,
110
+ ghFailure,
111
+ response.headers.get("x-ratelimit-remaining") === "0" ||
112
+ response.status === 429
113
+ ? "the unauthenticated GitHub API rate limit is exhausted"
114
+ : "the GitHub API refused the request",
115
+ );
116
+
117
+ if (!response.ok)
118
+ throw unreadable(
119
+ url,
120
+ ghFailure,
121
+ `the GitHub API returned HTTP ${response.status}`,
122
+ );
123
+
124
+ const parsed = z
125
+ .object({
126
+ number: z.number(),
127
+ title: z.string(),
128
+ base: z.object({ ref: z.string().min(1), sha: z.string() }),
129
+ })
130
+ .safeParse(await response.json().catch(() => undefined));
131
+
132
+ if (!parsed.success)
133
+ throw unreadable(url, ghFailure, "the GitHub API response was malformed");
134
+
135
+ return {
136
+ host,
137
+ slug,
138
+ headRef,
139
+ number: parsed.data.number,
140
+ title: parsed.data.title,
141
+ baseRefName: parsed.data.base.ref,
142
+ baseRefOid: parsed.data.base.sha,
143
+ };
144
+ },
145
+
146
+ matchesRemote(remoteUrl, address) {
147
+ const remote = parseGitRemote(remoteUrl);
148
+
149
+ return (
150
+ remote?.host === address.host &&
151
+ remote.slug.toLowerCase() === address.slug.toLowerCase()
152
+ );
153
+ },
154
+ };
@@ -77,13 +77,16 @@ import {
77
77
  } from "./document.js";
78
78
  import { decodeImage } from "./image-decode.js";
79
79
  import { mapInputSchema } from "./map-input.js";
80
+ import {
81
+ type PullRequestAddress,
82
+ type PullRequestProvider,
83
+ resolvePullRequest,
84
+ } from "./provider.js";
80
85
  import {
81
86
  type PullRequestDeps,
82
87
  defaultPullRequestDeps,
83
88
  fetchPullRequest,
84
- githubRemotes,
85
- pullRequestAddress,
86
- readPullRequest,
89
+ remoteUrls,
87
90
  } from "./pull-request.js";
88
91
  import {
89
92
  type ResolvedPullRequest,
@@ -1145,20 +1148,20 @@ export class LocalReviewData {
1145
1148
  repository: { id?: string; preferred?: string },
1146
1149
  ): Promise<ResolvedPullRequest> {
1147
1150
  const deps = this.options.pullRequests ?? defaultPullRequestDeps;
1148
- const { host, slug, number } = pullRequestAddress(url);
1151
+ const { provider, address } = resolvePullRequest(url);
1149
1152
 
1150
1153
  // Only a host a registered checkout already fetches from is contacted.
1151
1154
  const checkout = await this.pullRequestCheckout(
1152
- host,
1153
- slug,
1155
+ provider,
1156
+ address,
1154
1157
  repository,
1155
1158
  deps,
1156
1159
  );
1157
1160
 
1158
- const pullRequest = await readPullRequest(url, deps);
1161
+ const pullRequest = await provider.readPullRequest(address, deps);
1159
1162
 
1160
1163
  const { head, base } = await fetchPullRequest(
1161
- { ...checkout, pullRequest },
1164
+ { ...checkout, provider: provider.id, pullRequest },
1162
1165
  deps,
1163
1166
  );
1164
1167
 
@@ -1167,13 +1170,13 @@ export class LocalReviewData {
1167
1170
  return {
1168
1171
  target: { kind: "commits", repositoryId, head, base },
1169
1172
  pins: { repositoryId, base, head },
1170
- title: pullRequest.title.trim() || `PR #${number}`,
1173
+ title: pullRequest.title.trim() || `PR #${address.number}`,
1171
1174
  };
1172
1175
  }
1173
1176
  /** A registered checkout with a remote for host/owner/repo, and that remote. */
1174
1177
  private async pullRequestCheckout(
1175
- host: string,
1176
- slug: string,
1178
+ provider: PullRequestProvider,
1179
+ address: PullRequestAddress,
1177
1180
  repository: { id?: string; preferred?: string },
1178
1181
  deps: PullRequestDeps,
1179
1182
  ) {
@@ -1196,10 +1199,8 @@ export class LocalReviewData {
1196
1199
 
1197
1200
  if (!vcs || !gitDir) continue;
1198
1201
 
1199
- const remote = (await githubRemotes(gitDir, deps)).find(
1200
- (entry) =>
1201
- entry.host === host &&
1202
- entry.slug.toLowerCase() === slug.toLowerCase(),
1202
+ const remote = (await remoteUrls(gitDir, deps)).find((entry) =>
1203
+ provider.matchesRemote(entry.url, address),
1203
1204
  );
1204
1205
 
1205
1206
  if (remote)
@@ -1212,6 +1213,7 @@ export class LocalReviewData {
1212
1213
  };
1213
1214
  }
1214
1215
 
1216
+ const { host, slug } = address;
1215
1217
  const name = host === "github.com" ? slug : `${host}/${slug}`;
1216
1218
 
1217
1219
  throw new ReviewInputError(
@@ -1,16 +1,13 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { pullRequestProvider } from "./provider.js";
3
4
  import type { Snapshot } from "./store.js";
4
5
 
5
6
  export const pullRequestUrl = z
6
7
  .string()
7
- .regex(
8
- /^https:\/\/[a-z0-9.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9]\d*$/,
9
- "Use a canonical GitHub PR URL: https://github.com/owner/repository/pull/123, or the same path on a GitHub Enterprise host.",
10
- )
11
8
  .refine(
12
- (url) => Number.isSafeInteger(Number(url.split("/").at(-1))),
13
- "PR number is too large.",
9
+ (url) => pullRequestProvider(url) !== undefined,
10
+ "Use a canonical GitHub PR URL: https://github.com/owner/repository/pull/123, or the same path on a GitHub Enterprise host.",
14
11
  );
15
12
 
16
13
  /** One key per PR: GitHub owner and repository names are case-insensitive,
@@ -0,0 +1,44 @@
1
+ import { ReviewInputError } from "./document.js";
2
+ import { githubProvider } from "./github-provider.js";
3
+ import type { PullRequestDeps, PullRequestRecord } from "./pull-request.js";
4
+
5
+ export interface PullRequestAddress {
6
+ host: string;
7
+ slug: string;
8
+ number: number;
9
+ }
10
+
11
+ export interface PullRequestProvider {
12
+ /** Names Review's ref namespace: refs/review/<id>/... */
13
+ id: string;
14
+ /** Recognizes this forge's PR URLs and extracts host, repo and number. */
15
+ parseUrl(url: string): PullRequestAddress | undefined;
16
+ /** Asks the forge for the PR's title, base branch and head ref. */
17
+ readPullRequest(
18
+ address: PullRequestAddress,
19
+ deps: PullRequestDeps,
20
+ ): Promise<PullRequestRecord>;
21
+ /** Picks the local remote to fetch the PR from. */
22
+ matchesRemote(remoteUrl: string, address: PullRequestAddress): boolean;
23
+ }
24
+
25
+ const providers: PullRequestProvider[] = [githubProvider];
26
+
27
+ export function pullRequestProvider(url: string) {
28
+ for (const provider of providers) {
29
+ const address = provider.parseUrl(url);
30
+
31
+ if (address) return { provider, address };
32
+ }
33
+
34
+ return undefined;
35
+ }
36
+
37
+ export function resolvePullRequest(url: string) {
38
+ const resolution = pullRequestProvider(url);
39
+
40
+ if (!resolution)
41
+ throw new ReviewInputError(`Unsupported pull request URL: ${url}`, 400);
42
+
43
+ return resolution;
44
+ }
@@ -1,9 +1,8 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
 
4
- import { type LocalVcsKind, parseGitRemote } from "@dev.fast/local-vcs";
4
+ import type { LocalVcsKind } from "@dev.fast/local-vcs";
5
5
  import { errorMessage } from "@dev.fast/trace-core";
6
- import { z } from "zod";
7
6
 
8
7
  import { ReviewInputError } from "./document.js";
9
8
 
@@ -25,15 +24,13 @@ export interface PullRequestRecord {
25
24
  slug: string;
26
25
  number: number;
27
26
  title: string;
27
+ /** e.g. refs/pull/7/head */
28
+ headRef: string;
28
29
  baseRefName: string;
29
30
  /** GitHub's base commit, frozen at the PR's last update. */
30
31
  baseRefOid?: string;
31
32
  }
32
33
 
33
- const GH_TIMEOUT_MS = 20_000;
34
-
35
- const API_TIMEOUT_MS = 15_000;
36
-
37
34
  const FETCH_TIMEOUT_MS = 120_000;
38
35
 
39
36
  const LOCAL_TIMEOUT_MS = 30_000;
@@ -71,127 +68,12 @@ export const defaultPullRequestDeps: PullRequestDeps = {
71
68
  fetch: (input, init) => fetch(input, init),
72
69
  };
73
70
 
74
- /** Host, owner/repo and number from a canonical PR URL (validated by the command schema). */
75
- export function pullRequestAddress(url: string) {
76
- const { hostname, pathname } = new URL(url);
77
- const [, owner, repo, , number] = pathname.split("/");
78
-
79
- return { host: hostname, slug: `${owner}/${repo}`, number: Number(number) };
80
- }
81
-
82
- const metadataSchema = z.object({
83
- number: z.number().int().positive(),
84
- title: z.string(),
85
- baseRefName: z.string().min(1),
86
- baseRefOid: z.string().optional(),
87
- });
88
-
89
- /** gh first (it carries the user's auth), then, for github.com only, GitHub's
90
- * public REST API: Enterprise hosts require gh. */
91
- export async function readPullRequest(
92
- url: string,
93
- deps: PullRequestDeps,
94
- ): Promise<PullRequestRecord> {
95
- const { host, slug, number } = pullRequestAddress(url);
96
- let ghFailure: string;
97
-
98
- try {
99
- const stdout = await deps.run(
100
- "gh",
101
- [
102
- "pr",
103
- "view",
104
- String(number),
105
- "--repo",
106
- `${host}/${slug}`,
107
- "--json",
108
- "number,title,baseRefName,baseRefOid",
109
- ],
110
- { timeoutMs: GH_TIMEOUT_MS },
111
- );
112
-
113
- return { host, slug, ...metadataSchema.parse(JSON.parse(stdout)) };
114
- } catch (error) {
115
- ghFailure = firstLine(errorMessage(error));
116
- }
117
-
118
- if (host !== "github.com")
119
- throw new ReviewInputError(
120
- `Could not read ${url}: gh failed (${ghFailure}). Run \`gh auth login --hostname ${host}\` with an account that can read the repository, then retry.`,
121
- 409,
122
- );
123
-
124
- let response: Response;
125
-
126
- try {
127
- response = await deps.fetch(
128
- `https://api.github.com/repos/${slug}/pulls/${number}`,
129
- {
130
- headers: {
131
- Accept: "application/vnd.github+json",
132
- "X-GitHub-Api-Version": "2022-11-28",
133
- },
134
- signal: AbortSignal.timeout(API_TIMEOUT_MS),
135
- },
136
- );
137
- } catch (error) {
138
- throw unreadable(
139
- url,
140
- ghFailure,
141
- `the GitHub API is unreachable (${firstLine(errorMessage(error))})`,
142
- );
143
- }
144
-
145
- if (response.status === 404)
146
- throw new ReviewInputError(
147
- `${url} was not found: it does not exist, or it is private and gh is not signed in to an account that can read it (run \`gh auth status\`). gh said: ${ghFailure}`,
148
- 404,
149
- );
150
-
151
- if (response.status === 403 || response.status === 429)
152
- throw unreadable(
153
- url,
154
- ghFailure,
155
- response.headers.get("x-ratelimit-remaining") === "0" ||
156
- response.status === 429
157
- ? "the unauthenticated GitHub API rate limit is exhausted"
158
- : "the GitHub API refused the request",
159
- );
160
-
161
- if (!response.ok)
162
- throw unreadable(
163
- url,
164
- ghFailure,
165
- `the GitHub API returned HTTP ${response.status}`,
166
- );
167
-
168
- const parsed = z
169
- .object({
170
- number: z.number(),
171
- title: z.string(),
172
- base: z.object({ ref: z.string().min(1), sha: z.string() }),
173
- })
174
- .safeParse(await response.json().catch(() => undefined));
175
-
176
- if (!parsed.success)
177
- throw unreadable(url, ghFailure, "the GitHub API response was malformed");
178
-
179
- return {
180
- host,
181
- slug,
182
- number: parsed.data.number,
183
- title: parsed.data.title,
184
- baseRefName: parsed.data.base.ref,
185
- baseRefOid: parsed.data.base.sha,
186
- };
187
- }
188
-
189
- /** Remotes by the host and owner/repo their configured URL names. The
190
- * configured URL, not the insteadOf rewrite: a mirror still names its repo. */
191
- export async function githubRemotes(
71
+ /** Remotes by their configured URL, not the insteadOf rewrite: a mirror
72
+ * still names its repo. */
73
+ export async function remoteUrls(
192
74
  gitDir: string,
193
75
  deps: PullRequestDeps,
194
- ): Promise<{ name: string; host: string; slug: string }[]> {
76
+ ): Promise<{ name: string; url: string }[]> {
195
77
  const stdout = await deps
196
78
  .run(
197
79
  "git",
@@ -208,23 +90,16 @@ export async function githubRemotes(
208
90
 
209
91
  return stdout.split("\n").flatMap((line) => {
210
92
  const match = /^remote\.(.+)\.url\s+(\S+)/.exec(line.trim());
211
- const remote = match && parseGitRemote(match[2]!);
212
93
 
213
- return match && remote
214
- ? [{ name: match[1]!, host: remote.host, slug: remote.slug }]
215
- : [];
94
+ return match ? [{ name: match[1]!, url: match[2]! }] : [];
216
95
  });
217
96
  }
218
97
 
219
98
  /** Refs Review owns for one PR; the user's branches and bookmarks never move. */
220
- export function pullRequestRefs(pr: {
221
- host: string;
222
- slug: string;
223
- number: number;
224
- }) {
99
+ function pullRequestRefs(provider: string, pr: PullRequestRecord) {
225
100
  // github.com keeps the host-less namespace existing checkouts already hold.
226
101
  const repository = `${pr.host === "github.com" ? "" : `${pr.host}/`}${pr.slug}`;
227
- const prefix = `refs/review/github/${repository.toLowerCase()}/pull/${pr.number}`;
102
+ const prefix = `refs/review/${provider}/${repository.toLowerCase()}/pull/${pr.number}`;
228
103
 
229
104
  return {
230
105
  head: `${prefix}/head`,
@@ -250,12 +125,13 @@ export async function fetchPullRequest(
250
125
  gitDir: string;
251
126
  kind: LocalVcsKind;
252
127
  remote: string;
128
+ provider: string;
253
129
  pullRequest: PullRequestRecord;
254
130
  },
255
131
  deps: PullRequestDeps,
256
132
  ): Promise<{ head: string; base: string }> {
257
133
  const { pullRequest: pr } = input;
258
- const refs = pullRequestRefs(pr);
134
+ const refs = pullRequestRefs(input.provider, pr);
259
135
 
260
136
  const git = (args: string[], timeoutMs = LOCAL_TIMEOUT_MS) =>
261
137
  deps
@@ -298,14 +174,14 @@ export async function fetchPullRequest(
298
174
 
299
175
  try {
300
176
  await fetchRefs(
301
- `+refs/pull/${pr.number}/head:${refs.head}`,
177
+ `+${pr.headRef}:${refs.head}`,
302
178
  `+refs/heads/${pr.baseRefName}:${refs.base}`,
303
179
  );
304
180
  baseTip = await commit(refs.base);
305
181
  } catch (error) {
306
182
  // The base branch may be gone; the head and the frozen base suffice.
307
183
  try {
308
- await fetchRefs(`+refs/pull/${pr.number}/head:${refs.head}`);
184
+ await fetchRefs(`+${pr.headRef}:${refs.head}`);
309
185
  } catch {
310
186
  throw new ReviewInputError(
311
187
  `Could not fetch PR #${pr.number} from remote "${input.remote}" (${pr.slug}). Check \`git fetch ${input.remote}\` works with your Git credentials, then retry. Git said: ${firstLine(errorMessage(error))}`,
@@ -377,13 +253,6 @@ async function indexForJj(
377
253
  }
378
254
  }
379
255
 
380
- function unreadable(url: string, ghFailure: string, fallback: string) {
381
- return new ReviewInputError(
382
- `Could not read ${url}: gh failed (${ghFailure}) and ${fallback}. Run \`gh auth status\` and sign in with an account that can read the repository, then retry.`,
383
- 409,
384
- );
385
- }
386
-
387
- function firstLine(text: string) {
256
+ export function firstLine(text: string) {
388
257
  return text.split("\n")[0]!.slice(0, 300);
389
258
  }
@@ -1,2 +0,0 @@
1
- import { n as runReviewAgentCli } from "./agent-cli-BBPxwUOd.js";
2
- export { runReviewAgentCli };
@@ -1,2 +0,0 @@
1
- import { t as openReviewProfile } from "./profile-BsYDBr7t.js";
2
- export { openReviewProfile };