@dev.fast/whiteboard 0.3.5-preview.20261022.134 → 0.3.5-preview.20261023.136

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.
@@ -3,7 +3,7 @@ import { c as jsonString, m as isStringValue, n as isJsonObject, o as jsonObject
3
3
  import { F as structuralChangeCounts, P as decodeStructuralDiffEvent, c as boardIconKind, l as boardTitle } from "./src-DYHVzX3b.js";
4
4
  import "./src-Z1x4-Qmb.js";
5
5
  import { b as updateCoverage, c as parseAnchor, f as sourceAnchors, g as intersectIntervals, h as emptyCoverage, p as coverageSchema, v as subtractIntervals, y as unionIntervals } from "./lens-selection-9byzEgvU.js";
6
- import { A as elements, B as respellCommitLinks, C as assignFreshIds, D as commitLinkCommits, E as boardRepositoryId, F as pinsSchema, G as sourceReferences, H as respellEditLinks, J as documentAdvice, K as summarizeEdit, N as pinDocumentReferences, O as documentSchema, P as pinnedRepositoryIds, Q as lensEditSchema, R as resourceReference, S as applyEdit, T as boardLinks, U as reviewTargetSchema, V as respellDocumentLinks, Z as applyLensEdit, _ as remoteUrls, a as SoftwareModelValidationError, at as sliceSourceRange, b as adoptFlowUnits, c as traceSchema, ct as parseMarkdown, d as setPullRequest, et as lensSelections, g as fetchPullRequest, h as defaultPullRequestDeps, i as readStoredDocument, it as requireVisibleSource, j as explicitPins, k as editSchema, l as pullRequestKey, m as GitHubRefusedRead, n as migrateStoredDocument, nt as checkSourcePath, o as defineSoftwareMap, ot as markdownNodes, p as resolvePullRequest, q as checkReferences, r as migrateStoredLenses, rt as fileLineRangeSchema, st as markdownText, tt as SourceRangeError, u as pullRequestUrl, v as azureRepositoryOf, w as assignMissingIds, x as anchorPins, y as textIncludesQuote, z as resourceReferences } from "./stored-document-migration-B-lTO-_P.js";
6
+ import { $ as lensEditSchema, A as editSchema, B as resourceReferences, C as applyEdit, D as boardRepositoryId, E as boardLinks, F as pinnedRepositoryIds, H as respellDocumentLinks, I as pinsSchema, J as checkReferences, K as sourceReferences, M as explicitPins, O as commitLinkCommits, P as pinDocumentReferences, Q as applyLensEdit, S as anchorPins, T as assignMissingIds, U as respellEditLinks, V as respellCommitLinks, W as reviewTargetSchema, Y as documentAdvice, _ as fetchPullRequest, a as SoftwareModelValidationError, at as requireVisibleSource, b as textIncludesQuote, c as traceSchema, ct as markdownText, d as setPullRequest, f as sourceBranchName, g as defaultPullRequestDeps, h as GitHubRefusedRead, i as readStoredDocument, it as fileLineRangeSchema, j as elements, k as documentSchema, l as pullRequestKey, lt as parseMarkdown, m as resolvePullRequest, n as migrateStoredDocument, nt as SourceRangeError, o as defineSoftwareMap, ot as sliceSourceRange, q as summarizeEdit, r as migrateStoredLenses, rt as checkSourcePath, st as markdownNodes, tt as lensSelections, u as pullRequestUrl, v as remoteUrls, w as assignFreshIds, x as adoptFlowUnits, y as azureRepositoryOf, z as resourceReference } from "./stored-document-migration-DEt8iwem.js";
7
7
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
8
8
  import { A as processStartIdentity, L as writePrivateJsonAtomic, O as pidReused, j as withFileLock, k as processIsAlive } from "./s3-config-CjcLyFV5.js";
9
9
  import { t as errorMessage } from "./error-message-OtiDonty.js";
@@ -436,6 +436,57 @@ function commitsListed(output) {
436
436
  return new Set((output ?? "").split("\n").filter((line) => / commit /.test(line)).map((line) => line.split(" ")[0]));
437
437
  }
438
438
  //#endregion
439
+ //#region src/github-pr-graph.ts
440
+ const repository = z.object({ full_name: z.string() });
441
+ const pullRequest = z.object({
442
+ number: z.number().int().positive(),
443
+ title: z.string(),
444
+ draft: z.boolean().optional(),
445
+ head: z.object({
446
+ ref: z.string(),
447
+ repo: repository.nullable()
448
+ }),
449
+ base: z.object({
450
+ ref: z.string(),
451
+ repo: repository
452
+ })
453
+ });
454
+ const caches = /* @__PURE__ */ new WeakMap();
455
+ function readGitHubPullRequests(host, slug, run) {
456
+ let cache = caches.get(run);
457
+ if (!cache) caches.set(run, cache = /* @__PURE__ */ new Map());
458
+ const key = `${host}/${slug}`.toLowerCase();
459
+ const previous = cache.get(key);
460
+ if (previous && previous.expires > Date.now()) return previous.read;
461
+ const read = (async () => {
462
+ const result = [];
463
+ for (let page = 1; page <= 10; page++) {
464
+ const records = z.array(pullRequest).parse(JSON.parse(await run(host, `repos/${slug}/pulls?state=open&per_page=100&page=${page}`)));
465
+ for (const pr of records) {
466
+ if (pr.head.repo?.full_name.toLowerCase() !== slug.toLowerCase() || pr.base.repo.full_name.toLowerCase() !== slug.toLowerCase()) continue;
467
+ result.push({
468
+ number: pr.number,
469
+ url: `https://${host}/${slug}/pull/${pr.number}`,
470
+ title: pr.title,
471
+ headBranch: pr.head.ref,
472
+ baseBranch: pr.base.ref,
473
+ draft: pr.draft ?? false
474
+ });
475
+ }
476
+ if (records.length < 100) return result;
477
+ }
478
+ throw new Error("PR discovery exceeded 1000 open pull requests; refusing a partial stack.");
479
+ })();
480
+ cache.set(key, {
481
+ expires: Date.now() + 6e4,
482
+ read
483
+ });
484
+ read.catch(() => {
485
+ if (cache.get(key)?.read === read) cache.delete(key);
486
+ });
487
+ return read;
488
+ }
489
+ //#endregion
439
490
  //#region src/review-checkout-paths.ts
440
491
  /**
441
492
  * Root for Review-owned checkouts. A Review UUID identifies one subtree.
@@ -2663,7 +2714,8 @@ const commandSchema = z.strictObject({ operation: z.discriminatedUnion("type", [
2663
2714
  z.strictObject({
2664
2715
  type: z.literal("set_target"),
2665
2716
  reviewId,
2666
- target: reviewTargetSchema,
2717
+ target: reviewTargetSchema.optional(),
2718
+ repositoryId: z.string().min(1).optional(),
2667
2719
  pullRequestUrl: pullRequestUrl.nullable().optional()
2668
2720
  }),
2669
2721
  z.strictObject({
@@ -2747,6 +2799,7 @@ var ReviewStore = class {
2747
2799
  askHistory;
2748
2800
  db;
2749
2801
  pending = Promise.resolve();
2802
+ preparing = /* @__PURE__ */ new Set();
2750
2803
  closing = false;
2751
2804
  liveSources = /* @__PURE__ */ new Map();
2752
2805
  refreshTimer;
@@ -3070,6 +3123,11 @@ var ReviewStore = class {
3070
3123
  repositoryGroup(root) {
3071
3124
  return this.checkouts.group(root);
3072
3125
  }
3126
+ sameRepository(left, right) {
3127
+ if (left === right) return true;
3128
+ const group = this.repositoryGroup(this.repositoryPath(left));
3129
+ return group !== void 0 && group.key === this.repositoryGroup(this.repositoryPath(right))?.key;
3130
+ }
3073
3131
  registerRepository(root) {
3074
3132
  this.db.prepare("INSERT OR IGNORE INTO repositories(id,path,name) VALUES(?,?,?)").run(randomUUID(), root, root.split(/[\\/]/).at(-1));
3075
3133
  const row = this.db.prepare("SELECT id,name FROM repositories WHERE path=?").get(root);
@@ -3082,6 +3140,7 @@ var ReviewStore = class {
3082
3140
  /** Forgets a checkout no board uses, after every write queued before it,
3083
3141
  * so a board being made with it keeps it. */
3084
3142
  unregisterRepository(id) {
3143
+ if (this.preparing.size) return Promise.allSettled([...this.preparing]).then(() => this.unregisterRepository(id));
3085
3144
  const run = this.pending.then(async () => {
3086
3145
  this.db.exec("BEGIN IMMEDIATE");
3087
3146
  try {
@@ -3152,6 +3211,7 @@ var ReviewStore = class {
3152
3211
  clearInterval(this.externalChanges);
3153
3212
  clearInterval(this.refreshTimer);
3154
3213
  clearInterval(this.purgeTimer);
3214
+ await Promise.allSettled([...this.preparing]);
3155
3215
  await this.pending;
3156
3216
  this.listeners.clear();
3157
3217
  this.catalogListeners.clear();
@@ -3354,19 +3414,45 @@ var ReviewStore = class {
3354
3414
  const pullRequest = this.startPullRequest(command);
3355
3415
  pullRequest?.catch(() => {});
3356
3416
  const pullRequestTitle = pullRequest ? pullRequest.then(({ title }) => title || void 0, () => void 0) : this.startPullRequestTitle(command);
3417
+ const prepared = this.prepareCommand(command, pullRequest, pullRequestTitle, author).then((source) => this.applyPreparedCommand(source, initial, author));
3418
+ this.preparing.add(prepared);
3419
+ prepared.finally(() => this.preparing.delete(prepared)).catch(() => {});
3420
+ return prepared;
3421
+ }
3422
+ /** Resolve optional network context before entering the write queue. */
3423
+ async prepareCommand(command, pullRequest, pullRequestTitle, author) {
3424
+ const op = command.operation;
3425
+ if (op.type === "create") {
3426
+ if (!op.target && !op.pullRequestUrl) throw new ReviewInputError("Supply a target or a pullRequestUrl.");
3427
+ if (op.repositoryId && op.target) throw new ReviewInputError("A checkout for the PR applies only to a create from pullRequestUrl alone; name it in the target instead.");
3428
+ if (!op.title && op.target && author.kind !== "person") throw new ReviewInputError("Supply a title.");
3429
+ }
3430
+ if (op.type === "set_target" && !op.target && !op.pullRequestUrl) throw new ReviewInputError("Supply a target or a pullRequestUrl.");
3431
+ const requestedTarget = op.type === "create" || op.type === "set_target" ? op.target : void 0;
3432
+ const fromPullRequest = await pullRequest;
3433
+ const resolvedTarget = requestedTarget?.kind === "project" ? (this.repositoryPath(requestedTarget.repositoryId), { target: requestedTarget }) : requestedTarget ? await (this.providers.resolveTarget?.(requestedTarget) ?? namedCommits(requestedTarget)) : fromPullRequest;
3434
+ if (requestedTarget && !resolvedTarget) throw new ReviewInputError("Review targets are unavailable.");
3435
+ const resolvedBranch = fromPullRequest ? fromPullRequest.branch : resolvedTarget?.pins && this.providers.headBranch ? await this.providers.headBranch(resolvedTarget.pins, requestedTarget?.kind === "commits" ? requestedTarget.head : void 0) : void 0;
3436
+ return {
3437
+ op,
3438
+ requestedTarget,
3439
+ fromPullRequest,
3440
+ resolvedTarget,
3441
+ resolvedBranch,
3442
+ discoveredPullRequest: resolvedTarget?.pins && resolvedBranch && (op.type === "create" || op.type === "set_target") && op.pullRequestUrl === void 0 ? await this.providers.findPullRequest?.(resolvedTarget.pins, resolvedBranch).catch(() => void 0) : void 0,
3443
+ resolvedTitle: await pullRequestTitle
3444
+ };
3445
+ }
3446
+ applyPreparedCommand(prepared, initial, author) {
3447
+ const { op, requestedTarget, fromPullRequest, resolvedTarget, resolvedBranch, discoveredPullRequest, resolvedTitle } = prepared;
3357
3448
  return this.enqueue(async () => {
3358
- const op = command.operation;
3359
- if (op.type === "create") {
3360
- if (!op.target && !op.pullRequestUrl) throw new ReviewInputError("Supply a target or a pullRequestUrl.");
3361
- if (op.repositoryId && op.target) throw new ReviewInputError("A checkout for the PR applies only to a create from pullRequestUrl alone; name it in the target instead.");
3362
- if (!op.title && op.target && author.kind !== "person") throw new ReviewInputError("Supply a title.");
3363
- }
3364
- const requestedTarget = op.type === "create" || op.type === "set_target" ? op.target : void 0;
3365
- const fromPullRequest = await pullRequest;
3366
- const resolvedTarget = requestedTarget?.kind === "project" ? (this.repositoryPath(requestedTarget.repositoryId), { target: requestedTarget }) : requestedTarget ? await (this.providers.resolveTarget?.(requestedTarget) ?? namedCommits(requestedTarget)) : fromPullRequest;
3367
- if (requestedTarget && !resolvedTarget) throw new ReviewInputError("Review targets are unavailable.");
3368
- if (op.type === "create" && op.pullRequestUrl && op.reuseExisting !== false) {
3369
- const [found, ...others] = this.reviewsForPullRequest(op.pullRequestUrl);
3449
+ if (op.type === "create" && (op.pullRequestUrl ? op.reuseExisting !== false : op.reuseExisting && resolvedTarget?.pins)) {
3450
+ const known = op.pullRequestUrl ? this.reviewsForPullRequest(op.pullRequestUrl) : [];
3451
+ const branch = fromPullRequest?.branch ?? resolvedBranch;
3452
+ const repository = resolvedTarget?.target.repositoryId;
3453
+ const project = repository && this.projectOfRepository(repository);
3454
+ const matching = !known.length && branch && project ? this.list().filter((review) => review.project?.id === project && sourceBranchName(review.origin?.branch) === sourceBranchName(branch) && (op.pullRequestUrl ? !review.origin?.pullRequestUrl : !review.trashedAt) && review.pins !== void 0 && repository !== void 0 && this.sameRepository(review.pins.repositoryId, repository)).reverse().map((review) => review.reviewId) : [];
3455
+ const [found, ...others] = [...known, ...matching];
3370
3456
  if (found) {
3371
3457
  const result = this.existingReview(found, others, resolvedTarget.pins);
3372
3458
  if (op.parentId || op.dependsOn) result.note += ` Its parent and dependencies were not changed either: set them with session_set_parent and session_set_dependencies.`;
@@ -3389,17 +3475,10 @@ var ReviewStore = class {
3389
3475
  let lensTarget;
3390
3476
  /** The head a worktree board read at, when it becomes a plan. */
3391
3477
  let unpinnedWorktree;
3392
- if ((op.type === "create" || op.type === "set_target") && this.providers.headBranch) {
3393
- const pins = resolvedTarget?.pins ?? snapshot.pins;
3394
- if (pins) {
3395
- const headRef = requestedTarget?.kind === "commits" ? requestedTarget.head : void 0;
3396
- const branch = await this.providers.headBranch(pins, headRef);
3397
- snapshot.origin = {
3398
- ...snapshot.origin,
3399
- branch
3400
- };
3401
- }
3402
- }
3478
+ if ((op.type === "create" || op.type === "set_target") && resolvedTarget?.pins && (this.providers.headBranch || fromPullRequest)) snapshot.origin = {
3479
+ ...snapshot.origin,
3480
+ branch: resolvedBranch
3481
+ };
3403
3482
  if (!requestedTarget && fromPullRequest) snapshot.origin = {
3404
3483
  ...snapshot.origin,
3405
3484
  branch: fromPullRequest.branch ?? snapshot.origin?.branch,
@@ -3423,13 +3502,13 @@ var ReviewStore = class {
3423
3502
  };
3424
3503
  for (const block of snapshot.document) assignFreshIds(block, (prefix) => `${prefix}-${++nextId}`);
3425
3504
  }
3426
- setPullRequest(snapshot, op.pullRequestUrl, await pullRequestTitle);
3505
+ setPullRequest(snapshot, op.pullRequestUrl, resolvedTitle);
3427
3506
  break;
3428
3507
  case "rename":
3429
3508
  snapshot.title = op.title;
3430
3509
  break;
3431
3510
  case "set_target":
3432
- setPullRequest(snapshot, op.pullRequestUrl ?? (op.pullRequestUrl === null || snapshot.target?.repositoryId !== resolvedTarget.target.repositoryId ? null : void 0), await pullRequestTitle);
3511
+ setPullRequest(snapshot, op.pullRequestUrl ?? (op.pullRequestUrl === null || snapshot.target?.repositoryId !== resolvedTarget.target.repositoryId ? null : void 0), resolvedTitle);
3433
3512
  snapshot.staleSources = [];
3434
3513
  if (snapshot.pins && !resolvedTarget.pins) {
3435
3514
  const pinned = pinDocumentReferences(snapshot.document, snapshot.pins);
@@ -3469,6 +3548,14 @@ var ReviewStore = class {
3469
3548
  break;
3470
3549
  }
3471
3550
  }
3551
+ if ((op.type === "create" || op.type === "set_target") && snapshot.pins) {
3552
+ const branch = sourceBranchName(snapshot.origin?.branch);
3553
+ const sameBranch = previous?.pins?.repositoryId === snapshot.pins.repositoryId && sourceBranchName(previous.origin?.branch) === branch;
3554
+ if (op.pullRequestUrl === null) setPullRequest(snapshot, null);
3555
+ else if (op.pullRequestUrl) setPullRequest(snapshot, op.pullRequestUrl, fromPullRequest?.title, fromPullRequest?.baseBranch);
3556
+ else if (branch && discoveredPullRequest) setPullRequest(snapshot, discoveredPullRequest.url, discoveredPullRequest.title, discoveredPullRequest.baseBranch);
3557
+ else if (previous && !sameBranch) setPullRequest(snapshot, null);
3558
+ }
3472
3559
  const warnings = unpinnedWorktree ? [`Its references now read at ${unpinnedWorktree.slice(0, 7)}, the checkout's last commit, which lacks any uncommitted lines they quoted. Check them.`] : [];
3473
3560
  return this.commitVersion({
3474
3561
  snapshot,
@@ -3593,9 +3680,10 @@ var ReviewStore = class {
3593
3680
  currentVersion(reviewId) {
3594
3681
  return Number(this.db.prepare("SELECT version FROM reviews WHERE id=?").get(reviewId).version);
3595
3682
  }
3596
- /** Runs one write after every write queued before it. */
3683
+ /** Runs an accepted write after every write queued before it. Admission
3684
+ * checks belong at entry points: preparations accepted before close still
3685
+ * need to enqueue their writes while close waits for them. */
3597
3686
  enqueue(work) {
3598
- if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("Review store is closing."));
3599
3687
  const run = this.pending.then(work);
3600
3688
  this.pending = run.catch(() => {});
3601
3689
  return run;
@@ -3695,6 +3783,7 @@ var ReviewStore = class {
3695
3783
  async discard(reviewId) {
3696
3784
  const deadline = Date.now() + FOCUS_LEASE_TTL_MS;
3697
3785
  while (this.leased(reviewId) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, DISCARD_POLL_MS));
3786
+ if (this.closing) throw new Error("Review store is closing.");
3698
3787
  return this.enqueue(async () => {
3699
3788
  if (!this.has(reviewId)) return { deleted: false };
3700
3789
  const snapshot = this.read(reviewId);
@@ -3745,6 +3834,7 @@ var ReviewStore = class {
3745
3834
  * they started from. Blocks without ids get fresh ones. */
3746
3835
  personEdit(reviewId, input) {
3747
3836
  const body = personEditSchema.parse(input);
3837
+ if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("Review store is closing."));
3748
3838
  return this.enqueue(async () => {
3749
3839
  const previous = this.read(reviewId);
3750
3840
  assertBaseVersion(body.baseVersion, previous.version);
@@ -4137,10 +4227,10 @@ var ReviewStore = class {
4137
4227
  /** Resolve a target-less PR create before it queues, when it will need it. */
4138
4228
  startPullRequest(command) {
4139
4229
  const op = command.operation;
4140
- if (op.type !== "create" || op.target || !op.pullRequestUrl) return void 0;
4230
+ if (op.type !== "create" && op.type !== "set_target" || op.target || !op.pullRequestUrl) return void 0;
4141
4231
  if (!this.providers.resolvePullRequest) return Promise.reject(new ReviewInputError("Pull request targets are unavailable."));
4142
4232
  if (!op.repositoryId) return Promise.reject(new ReviewInputError("Name the checkout to fetch the PR into: target {kind:\"pullRequest\", repositoryPath, pullRequest}."));
4143
- const [existing] = op.reuseExisting === false ? [] : this.reviewsForPullRequest(op.pullRequestUrl);
4233
+ const [existing] = op.type !== "create" || op.reuseExisting === false ? [] : this.reviewsForPullRequest(op.pullRequestUrl);
4144
4234
  if (existing) try {
4145
4235
  this.assertReusableReview(existing);
4146
4236
  } catch (error) {
@@ -5552,23 +5642,67 @@ var LocalReviewData = class {
5552
5642
  if (!existsSync(this.store.repositoryPath(snapshot.pins.repositoryId))) throw unavailableCheckout();
5553
5643
  return snapshot.pins;
5554
5644
  }
5645
+ /** Optional forge context; never changes the comparison's pins. */
5646
+ readGitHubApi = (host, endpoint) => (this.options.pullRequests ?? defaultPullRequestDeps).run("gh", [
5647
+ "api",
5648
+ "--hostname",
5649
+ { value: host },
5650
+ { value: endpoint }
5651
+ ], { timeoutMs: 1e4 });
5652
+ async findPullRequest(pins, branch) {
5653
+ const context = await resolveRepoContext(this.store.repositoryPath(pins.repositoryId));
5654
+ if (!context?.githubSlug || !context.originUrl) return void 0;
5655
+ const remote = parseGitRemote(context.originUrl);
5656
+ if (!remote) return void 0;
5657
+ const name = sourceBranchName(branch);
5658
+ const matches = (await readGitHubPullRequests(remote.host, context.githubSlug, this.readGitHubApi)).filter((pr) => pr.headBranch === name);
5659
+ return matches.length === 1 ? matches[0] : void 0;
5660
+ }
5661
+ /** Enrich current display metadata without repinning or rewriting history. */
5662
+ async discoverPullRequest(snapshot) {
5663
+ const { origin, pins } = snapshot;
5664
+ if (!pins || !origin?.branch) return snapshot;
5665
+ const found = await this.findPullRequest(pins, origin.branch).catch(() => void 0);
5666
+ if (!found) return snapshot;
5667
+ if (origin.pullRequestUrl && pullRequestKey(origin.pullRequestUrl) !== pullRequestKey(found.url)) return snapshot;
5668
+ return {
5669
+ ...snapshot,
5670
+ origin: {
5671
+ ...origin,
5672
+ pullRequestUrl: found.url,
5673
+ pullRequestNumber: found.number,
5674
+ pullRequestTitle: found.title,
5675
+ pullRequestBaseBranch: found.baseBranch
5676
+ }
5677
+ };
5678
+ }
5555
5679
  /** Capture a label only when its ref still resolves to these exact pins. */
5556
5680
  async headBranch(pins, headRef) {
5681
+ const context = await resolveRepoContext(this.store.repositoryPath(pins.repositoryId));
5682
+ if (!context) return void 0;
5557
5683
  const vcs = await this.vcs(pins.repositoryId);
5558
- if (!vcs || vcs.kind !== "git") return void 0;
5684
+ const symbolicRef = headRef && headRef !== "HEAD" && headRef !== "@" && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(headRef) ? headRef : void 0;
5685
+ let location;
5686
+ if (symbolicRef) location = ["--git-dir", context.commonDir];
5687
+ else if (vcs?.kind === "git") location = ["-C", vcs.rootPath];
5688
+ else return void 0;
5559
5689
  try {
5560
5690
  const { stdout } = await runVcs("git", [
5561
- "-C",
5562
- vcs.rootPath,
5691
+ ...location,
5563
5692
  "rev-parse",
5564
5693
  "--symbolic-full-name",
5565
5694
  "--verify",
5566
- !headRef || /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(headRef) ? "HEAD" : { rev: headRef }
5695
+ symbolicRef ? { rev: symbolicRef } : "HEAD"
5567
5696
  ]);
5568
5697
  const ref = stdout.trim();
5569
5698
  if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/remotes/")) return void 0;
5570
- if ((await vcs.resolveRevision(ref))?.commit !== pins.head) return void 0;
5571
- return ref.replace(/^refs\/(heads|remotes)\//, "");
5699
+ const { stdout: commit } = await runVcs("git", [
5700
+ ...location,
5701
+ "rev-parse",
5702
+ { rev: ref }
5703
+ ]);
5704
+ if (commit.trim() !== pins.head) return void 0;
5705
+ return ref.replace(/^refs\/remotes\/origin\//, "").replace(/^refs\/(heads|remotes)\//, "");
5572
5706
  } catch {
5573
5707
  return;
5574
5708
  }
@@ -6227,6 +6361,7 @@ function openLocalReviewStore(databasePath, options = {}) {
6227
6361
  resolveTarget: (target, pinned) => data.resolveTarget(target, pinned),
6228
6362
  resolvePullRequest: (url, repository) => data.resolvePullRequest(url, repository),
6229
6363
  pullRequestTitle: (url) => data.pullRequestTitle(url),
6364
+ findPullRequest: (pins, branch) => data.findPullRequest(pins, branch),
6230
6365
  headBranch: (pins, headRef) => data.headBranch(pins, headRef),
6231
6366
  sourcePins: (snapshot) => data.sourcePins(snapshot),
6232
6367
  unavailableAnchors: (snapshot) => data.unavailableAnchors(snapshot),
@@ -6273,4 +6408,4 @@ async function readWorkspace(file) {
6273
6408
  }
6274
6409
  }
6275
6410
  //#endregion
6276
- export { projectOfCheckout as a, activityBeginSchema as c, textualRows as d, invalidateStructuralComparisons as f, CheckoutGroups as h, inspectSnapshot as i, activityEndSchema as l, diffrMissingError as m, uploadSchema as n, groupCheckoutsAhead as o, diffrExecutable as p, commandSchema as r, initializeReviewStoreSchema as s, openLocalReviewStore as t, activityUpdateSchema as u };
6411
+ export { projectOfCheckout as a, activityBeginSchema as c, textualRows as d, invalidateStructuralComparisons as f, CheckoutGroups as g, readGitHubPullRequests as h, inspectSnapshot as i, activityEndSchema as l, diffrMissingError as m, uploadSchema as n, groupCheckoutsAhead as o, diffrExecutable as p, commandSchema as r, initializeReviewStoreSchema as s, openLocalReviewStore as t, activityUpdateSchema as u };
@@ -1,6 +1,6 @@
1
1
  import { a as toolResultText, s as ReviewApiError } from "./agent-client-Dc0vHFJt.js";
2
2
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
3
- import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-BBksBplC.js";
3
+ import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-D8EkIDjm.js";
4
4
  import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-C1L1vJyz.js";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -0,0 +1,2 @@
1
+ import { t as openReviewProfile } from "./profile-D9wdlPgw.js";
2
+ export { openReviewProfile };
@@ -1,4 +1,4 @@
1
- import { a as projectOfCheckout, h as CheckoutGroups, o as groupCheckoutsAhead, s as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-m4BCbtSA.js";
1
+ import { a as projectOfCheckout, g as CheckoutGroups, o as groupCheckoutsAhead, s as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-DHedfhxw.js";
2
2
  import "./src-Z1x4-Qmb.js";
3
3
  import { j as withFileLock } from "./s3-config-CjcLyFV5.js";
4
4
  import { access, mkdir } from "node:fs/promises";
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import { S as splitGitPatchFiles, a as diff, o as diffFileSummaries } from "../src-X9ZebeOD.js";
3
3
  import { c as jsonString, i as jsonBoolean, n as isJsonObject, p as isObjectValue, s as jsonProperty, u as parseJsonText } from "../src-X9phtB2j.js";
4
- import { f as StreamLimitError, i as SharedReviewStore, p as readBoundedStream } from "../client-CiB4MRlo.js";
4
+ import { f as StreamLimitError, i as SharedReviewStore, p as readBoundedStream } from "../client-p9WB8Sj0.js";
5
5
  import { A as ReviewGatewayHostSchema, D as ReviewConnectRequestSchema, N as reviewDiffrSummarizerInputSchema, d as ReviewBugReportRequestSchema, f as parseReviewBugReportResponse, m as REVIEW_CLIENT_REMOTE, p as REVIEW_CLIENT_HEADER, r as parseReviewDesktopVerbFrame, s as parseZod, t as parseReviewCliInstallApplyRequest, u as ReviewBugReportMetaV2Schema, y as REVIEW_HOST_HEADER } from "../src-DYHVzX3b.js";
6
6
  import "../src-Z1x4-Qmb.js";
7
- import { f as pullRequestProvider, z as resourceReferences } from "../stored-document-migration-B-lTO-_P.js";
7
+ import { B as resourceReferences, p as pullRequestProvider } from "../stored-document-migration-DEt8iwem.js";
8
8
  import { t as ReviewInputError } from "../input-error-OLgB_h31.js";
9
9
  import { L as writePrivateJsonAtomic, N as devReviewHome } from "../s3-config-CjcLyFV5.js";
10
10
  import { t as errorMessage } from "../error-message-OtiDonty.js";
11
11
  import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "../package-paths-BeYHYjeR.js";
12
12
  import { i as reviewInstanceDiscoveryPath, o as reviewLegacyDiscoveryPath } from "../review-home-paths-CG-sb9Cw.js";
13
13
  import { _ as reviewInstanceIdentity, u as REVIEW_INSTANCE_ENV } from "../server-discovery-DKLIOgg3.js";
14
- import { t as openReviewProfile } from "../profile-BfvRNmRc.js";
14
+ import { t as openReviewProfile } from "../profile-D9wdlPgw.js";
15
15
  import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-BM-_KdLi.js";
16
16
  import { l as REVIEW_TELEMETRY_TABS } from "../request-origin-CNSLVn7x.js";
17
17
  import { c as removeLegacyReviewSkills, d as skipCliInstall, h as detectConnectAgents, i as finishCliInstallUpdate, l as resetCliInstall, m as connectAgent, n as applyCliInstall, o as pathShimPath, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-2yTAxoBx.js";
18
- import { C as saveDiffrSummarizer, E as cliAskTools, S as readDiffrConfig, T as testDiffrSummarizer, _ as DEFAULT_MAX_REQUEST_BYTES, a as drainServerCrashReport, b as GlobalReviewDesktopVerbRelay, c as answerErrorsAsJson, d as legacyModeSchema, f as captureSanitizedUiTelemetry, g as readBoundedRequestJson, h as createNodeRequestListener, l as createWhiteboardCore, m as recordClientError, o as installProcessErrorTelemetry, p as clientErrorsForSession, r as takeOverServerHome, s as serverCrashReportPath, u as serverJson, v as HttpJsonError, w as setDiffrConfigValue, x as migrateDiffrConfig, y as ReviewServerError } from "../headless-host-D9y2sBIj.js";
19
- import { s as readQuerySchemas } from "../authoring-tools-BBksBplC.js";
18
+ import { C as saveDiffrSummarizer, E as cliAskTools, S as readDiffrConfig, T as testDiffrSummarizer, _ as DEFAULT_MAX_REQUEST_BYTES, a as drainServerCrashReport, b as GlobalReviewDesktopVerbRelay, c as answerErrorsAsJson, d as legacyModeSchema, f as captureSanitizedUiTelemetry, g as readBoundedRequestJson, h as createNodeRequestListener, l as createWhiteboardCore, m as recordClientError, o as installProcessErrorTelemetry, p as clientErrorsForSession, r as takeOverServerHome, s as serverCrashReportPath, u as serverJson, v as HttpJsonError, w as setDiffrConfigValue, x as migrateDiffrConfig, y as ReviewServerError } from "../headless-host-DJhcab6r.js";
19
+ import { s as readQuerySchemas } from "../authoring-tools-D8EkIDjm.js";
20
20
  import crypto, { createHash, randomBytes } from "node:crypto";
21
21
  import { z } from "zod";
22
22
  import fs, { existsSync, readFileSync } from "node:fs";
@@ -276,7 +276,7 @@ interface ReviewApiSummary {
276
276
  createdAt: string;
277
277
  /** Timestamp of the earliest retained review version, when known. */
278
278
  firstCreatedAt?: string;
279
- /** Present on reviews imported from the legacy MDX store. */
279
+ /** The branch and PR the review is of; see SnapshotOrigin. */
280
280
  origin?: {
281
281
  branch?: string;
282
282
  baseRef?: string;
@@ -284,6 +284,7 @@ interface ReviewApiSummary {
284
284
  pullRequestNumber?: number;
285
285
  pullRequestUrl?: string;
286
286
  pullRequestTitle?: string;
287
+ pullRequestBaseBranch?: string;
287
288
  revision?: string;
288
289
  };
289
290
  /** The PR's state on GitHub, when the host has read it. */
@@ -392,6 +393,16 @@ interface ReviewGatewayHostState {
392
393
  declined?: true;
393
394
  }
394
395
  //#endregion
396
+ //#region src/github-pr-graph.d.ts
397
+ interface GitHubPullRequest {
398
+ number: number;
399
+ url: string;
400
+ title: string;
401
+ headBranch: string;
402
+ baseBranch: string;
403
+ draft: boolean;
404
+ }
405
+ //#endregion
395
406
  //#region src/source.d.ts
396
407
  /** The repository and commits one source reference was read from. A
397
408
  * reference without pins resolves against its document's pins; one with
@@ -1387,6 +1398,8 @@ interface SnapshotOrigin {
1387
1398
  pullRequestUrl?: string;
1388
1399
  /** As the forge reported it when the PR was attached. */
1389
1400
  pullRequestTitle?: string;
1401
+ /** The branch the PR merges into, which can differ from baseBranch. */
1402
+ pullRequestBaseBranch?: string;
1390
1403
  /** The legacy review revision this version was imported from. */
1391
1404
  revision?: string;
1392
1405
  }
@@ -1497,6 +1510,7 @@ interface ResolvedPullRequest {
1497
1510
  baseBranch: string;
1498
1511
  }
1499
1512
  interface ReviewProviders {
1513
+ findPullRequest?(pins: Pins, branch: string): Promise<GitHubPullRequest | undefined>;
1500
1514
  headBranch?(pins: Pins, headRef?: string): Promise<string | undefined>;
1501
1515
  projectSource?(snapshot: Snapshot, pins: Pins): Promise<Snapshot>;
1502
1516
  /** Fetch a PR into the registered checkout the caller names. */
@@ -1542,6 +1556,7 @@ declare class ReviewStore {
1542
1556
  readonly askHistory: AskHistory;
1543
1557
  private readonly db;
1544
1558
  private pending;
1559
+ private readonly preparing;
1545
1560
  private closing;
1546
1561
  private readonly liveSources;
1547
1562
  private refreshTimer?;
@@ -1605,6 +1620,7 @@ declare class ReviewStore {
1605
1620
  /** How the store's checkouts group into projects, kept for the open. */
1606
1621
  private get checkouts();
1607
1622
  private repositoryGroup;
1623
+ private sameRepository;
1608
1624
  registerRepository(root: string): {
1609
1625
  id: string;
1610
1626
  name: string;
@@ -1671,6 +1687,9 @@ declare class ReviewStore {
1671
1687
  document: Block[];
1672
1688
  origin: SnapshotOrigin;
1673
1689
  }, author?: Author): Promise<Result>;
1690
+ /** Resolve optional network context before entering the write queue. */
1691
+ private prepareCommand;
1692
+ private applyPreparedCommand;
1674
1693
  /**
1675
1694
  * Commits a board's next version: the one place a version is written.
1676
1695
  * Whoever writes it, it keeps the same rules:
@@ -1690,7 +1709,9 @@ declare class ReviewStore {
1690
1709
  * project unless it moves repository. */
1691
1710
  private projectFor;
1692
1711
  private currentVersion;
1693
- /** Runs one write after every write queued before it. */
1712
+ /** Runs an accepted write after every write queued before it. Admission
1713
+ * checks belong at entry points: preparations accepted before close still
1714
+ * need to enqueue their writes while close waits for them. */
1694
1715
  private enqueue;
1695
1716
  /** Respells a document's links and resolves its commit links, as every
1696
1717
  * save does. A changed reference is no longer stale. */
@@ -2059,6 +2080,11 @@ declare class LocalReviewData {
2059
2080
  /** Throws 404 when the checkout behind the snapshot is gone. Undefined
2060
2081
  * for a document without default pins. */
2061
2082
  sourcePins(snapshot: Snapshot): Promise<Pins | undefined>;
2083
+ /** Optional forge context; never changes the comparison's pins. */
2084
+ private readonly readGitHubApi;
2085
+ findPullRequest(pins: Pins, branch: string): Promise<GitHubPullRequest | undefined>;
2086
+ /** Enrich current display metadata without repinning or rewriting history. */
2087
+ discoverPullRequest(snapshot: Snapshot): Promise<Snapshot>;
2062
2088
  /** Capture a label only when its ref still resolves to these exact pins. */
2063
2089
  headBranch(pins: Pins, headRef?: string): Promise<string | undefined>;
2064
2090
  /** `pinned` refreshes a stored target: a worktree base ref resolves to its
@@ -2274,8 +2300,10 @@ declare function validateShareBundle(bundle: ShareBundle): {
2274
2300
  [x: string]: any;
2275
2301
  branch?: string | undefined;
2276
2302
  baseRef?: string | undefined;
2303
+ baseBranch?: string | undefined;
2277
2304
  pullRequestNumber?: number | undefined;
2278
2305
  pullRequestTitle?: string | undefined;
2306
+ pullRequestBaseBranch?: string | undefined;
2279
2307
  revision?: string | undefined;
2280
2308
  } | undefined;
2281
2309
  };
@@ -2344,8 +2372,10 @@ declare class SharedReviewStore {
2344
2372
  [x: string]: any;
2345
2373
  branch?: string | undefined;
2346
2374
  baseRef?: string | undefined;
2375
+ baseBranch?: string | undefined;
2347
2376
  pullRequestNumber?: number | undefined;
2348
2377
  pullRequestTitle?: string | undefined;
2378
+ pullRequestBaseBranch?: string | undefined;
2349
2379
  revision?: string | undefined;
2350
2380
  } | undefined;
2351
2381
  };
@@ -1,2 +1,2 @@
1
- import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-CiB4MRlo.js";
1
+ import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-p9WB8Sj0.js";
2
2
  export { ShareClient, SharedReviewStore, exportShare, sharedReviewId, validateShareBundle, verifyShareRepository };
@@ -2484,22 +2484,29 @@ const pullRequestUrl = z.string().refine((url) => pullRequestProvider(url) !== v
2484
2484
  function pullRequestKey(url) {
2485
2485
  return url.toLowerCase();
2486
2486
  }
2487
- /** Omission preserves identity; null detaches it without changing import metadata. */
2488
- function setPullRequest(snapshot, url, title) {
2487
+ /** Older snapshots kept origin/ on remote branch labels. */
2488
+ const sourceBranchName = (branch) => branch?.replace(/^origin\//, "");
2489
+ /** Omission preserves identity; null removes the PR without changing import
2490
+ * metadata. A new url keeps the title and PR base when the caller omits them
2491
+ * for the same PR. */
2492
+ function setPullRequest(snapshot, url, title, baseBranch) {
2489
2493
  if (url === void 0) return;
2490
2494
  if (url === null) {
2491
2495
  if (snapshot.origin) {
2492
2496
  delete snapshot.origin.pullRequestUrl;
2493
2497
  delete snapshot.origin.pullRequestNumber;
2494
2498
  delete snapshot.origin.pullRequestTitle;
2499
+ delete snapshot.origin.pullRequestBaseBranch;
2495
2500
  }
2496
2501
  return;
2497
2502
  }
2503
+ const same = snapshot.origin?.pullRequestUrl === url;
2498
2504
  snapshot.origin = {
2499
2505
  ...snapshot.origin,
2500
2506
  pullRequestUrl: url,
2501
2507
  pullRequestNumber: Number(url.split("/").at(-1)),
2502
- pullRequestTitle: title ?? (snapshot.origin?.pullRequestUrl === url ? snapshot.origin.pullRequestTitle : void 0)
2508
+ pullRequestTitle: title ?? (same ? snapshot.origin?.pullRequestTitle : void 0),
2509
+ pullRequestBaseBranch: baseBranch ?? (same ? snapshot.origin?.pullRequestBaseBranch : void 0)
2503
2510
  };
2504
2511
  }
2505
2512
  //#endregion
@@ -3206,4 +3213,4 @@ function liftFileLenses(document) {
3206
3213
  };
3207
3214
  }
3208
3215
  //#endregion
3209
- export { lensSchema as $, elements as A, respellCommitLinks as B, assignFreshIds as C, commitLinkCommits as D, boardRepositoryId as E, pinsSchema as F, sourceReferences as G, respellEditLinks as H, publishedEditSchema as I, documentAdvice as J, summarizeEdit as K, readsAtBoardPins as L, pathTargetSchema as M, pinDocumentReferences as N, documentSchema as O, pinnedRepositoryIds as P, lensEditSchema as Q, resourceReference as R, applyEdit as S, boardLinks as T, reviewTargetSchema as U, respellDocumentLinks as V, selectionReferences as W, UNCATEGORIZED_LENS_ID as X, stepSources as Y, applyLensEdit as Z, remoteUrls as _, SoftwareModelValidationError as a, sliceSourceRange as at, adoptFlowUnits as b, traceSchema as c, parseMarkdown as ct, setPullRequest as d, lensSelections as et, pullRequestProvider as f, fetchPullRequest as g, defaultPullRequestDeps as h, readStoredDocument as i, requireVisibleSource as it, explicitPins as j, editSchema as k, pullRequestKey as l, GitHubRefusedRead as m, migrateStoredDocument as n, checkSourcePath as nt, defineSoftwareMap as o, markdownNodes as ot, resolvePullRequest as p, checkReferences as q, migrateStoredLenses as r, fileLineRangeSchema as rt, softwareModelDataSchema as s, markdownText as st, liftFileLenses as t, SourceRangeError as tt, pullRequestUrl as u, azureRepositoryOf as v, assignMissingIds as w, anchorPins as x, textIncludesQuote as y, resourceReferences as z };
3216
+ export { lensEditSchema as $, editSchema as A, resourceReferences as B, applyEdit as C, boardRepositoryId as D, boardLinks as E, pinnedRepositoryIds as F, selectionReferences as G, respellDocumentLinks as H, pinsSchema as I, checkReferences as J, sourceReferences as K, publishedEditSchema as L, explicitPins as M, pathTargetSchema as N, commitLinkCommits as O, pinDocumentReferences as P, applyLensEdit as Q, readsAtBoardPins as R, anchorPins as S, assignMissingIds as T, respellEditLinks as U, respellCommitLinks as V, reviewTargetSchema as W, stepSources as X, documentAdvice as Y, UNCATEGORIZED_LENS_ID as Z, fetchPullRequest as _, SoftwareModelValidationError as a, requireVisibleSource as at, textIncludesQuote as b, traceSchema as c, markdownText as ct, setPullRequest as d, lensSchema as et, sourceBranchName as f, defaultPullRequestDeps as g, GitHubRefusedRead as h, readStoredDocument as i, fileLineRangeSchema as it, elements as j, documentSchema as k, pullRequestKey as l, parseMarkdown as lt, resolvePullRequest as m, migrateStoredDocument as n, SourceRangeError as nt, defineSoftwareMap as o, sliceSourceRange as ot, pullRequestProvider as p, summarizeEdit as q, migrateStoredLenses as r, checkSourcePath as rt, softwareModelDataSchema as s, markdownNodes as st, liftFileLenses as t, lensSelections as tt, pullRequestUrl as u, remoteUrls as v, assignFreshIds as w, adoptFlowUnits as x, azureRepositoryOf as y, resourceReference as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.3.5-preview.20261022.134",
3
+ "version": "0.3.5-preview.20261023.136",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -76,8 +76,8 @@
76
76
  "vitest": "4.1.11",
77
77
  "@dev.fast/review-protocol": "0.0.0",
78
78
  "@dev.fast/review-share-protocol": "0.1.2",
79
- "@dev.fast/trace-core": "0.0.0",
80
79
  "@dev.fast/json": "0.0.0",
80
+ "@dev.fast/trace-core": "0.0.0",
81
81
  "@dev.fast/trace-protocol": "0.5.0"
82
82
  },
83
83
  "engines": {
@@ -88,7 +88,7 @@
88
88
  "repo": "devdotfast/review",
89
89
  "docs": "packages/review/instructions/authoring.md"
90
90
  },
91
- "gitHead": "e6b58d262b3ad613be4acc3a53b79c8720ba6a0b",
91
+ "gitHead": "3fb855e33dc96595fdae83b3d4532e7a3fbeee58",
92
92
  "scripts": {
93
93
  "prebuild": "pnpm run build:workspace-deps",
94
94
  "build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",