@a9n-shoji/rvw 0.8.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.8.1] - 2026-09-14
9
+
10
+ ### Changed
11
+
12
+ - コメント対象とコメント内のcode参照を、現在選択中のcommitへ安全に配置できる場合はその比較documentで直接開き、変更・削除・曖昧な場合は投稿時点のsourceへfallbackするようにした
13
+
14
+ ### Fixed
15
+
16
+ - 承認後にコメントのみのreviewが追加されたPull Requestで、GitHub上の現在の承認数が0件として表示される問題を修正
17
+
8
18
  ## [0.8.0] - 2026-09-13
9
19
 
10
20
  ### Added
package/README.md CHANGED
@@ -112,7 +112,7 @@ viewerはこの流れのために次を提供します。
112
112
  - ファイル名fuzzy検索、Gitによるrealtime全文fixed-string検索(case / whole-word、file grouping、行jump)
113
113
  - PR全体、PR本文、ファイル全体、行範囲、Walkthrough全体へのコメント
114
114
  - コメントと返信のsafe GFM表示(repository内link/同一commit相対画像、表示専用Mermaid、
115
- exact commitへ固定したinline code referenceを含む)
115
+ global対象commitへ安全に配置し、できなければexact commitへfallbackするinline code referenceを含む)
116
116
  - 未解決/解決済み、返信、Outdated追跡
117
117
  - Agentへ渡す一件・一覧・複数選択した`rvw://comment/<uuid>`参照のコピー
118
118
  - Agentが提示したWalkthrough、exact code reference、選択可能なMermaid node
@@ -291,7 +291,8 @@ rvw Skillを使って、https://github.com/owner/repository/pull/123 の未解
291
291
  Agentにreview結果をRVWへ残してもらう場合は、対象PRとコメント作成を明示します。Agentはcommit済みの
292
292
  exact sourceを確認し、通常の未解決threadを一件ずつ作成します。指摘や回答の理解に別の実装箇所が
293
293
  必要なら、投稿本文の`rvw-ref:<id>` linkと同じ投稿に保存したtyped referenceから、その時点のcommitを
294
- globalなcommit選択を変えずに開けます。
294
+ globalなcommit選択を変えずに開けます。global対象commitへ安全に配置できるreferenceは現在の比較で直接開き、
295
+ 内容変更や削除などで配置できない場合は投稿時点のexact sourceへfallbackします。
295
296
 
296
297
  ```text
297
298
  rvw Skillを使って、https://github.com/owner/repository/pull/123 をreviewし、見つけた指摘をRVWのコメントとして作成してください。
package/dist/cli.mjs CHANGED
@@ -18762,7 +18762,7 @@ function parseWalkthroughUri(uri) {
18762
18762
  }
18763
18763
 
18764
18764
  // src/shared/constants.ts
18765
- var APP_VERSION = "0.8.0";
18765
+ var APP_VERSION = "0.8.1";
18766
18766
  var PROTOCOL_VERSION = 5;
18767
18767
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
18768
18768
  var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
@@ -21493,6 +21493,7 @@ function imageContentTypeHeader(contentType2) {
21493
21493
 
21494
21494
  // src/infrastructure/github/github-client.ts
21495
21495
  var ghPullRequestSchema = external_exports.object({
21496
+ id: external_exports.string().min(1),
21496
21497
  author: external_exports.object({ login: external_exports.string().min(1) }).nullable(),
21497
21498
  headRepository: external_exports.object({ name: external_exports.string().min(1) }).nullable(),
21498
21499
  headRepositoryOwner: external_exports.object({ login: external_exports.string().min(1) }).nullable(),
@@ -21507,17 +21508,36 @@ var ghPullRequestSchema = external_exports.object({
21507
21508
  baseRefOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
21508
21509
  headRefName: external_exports.string().min(1),
21509
21510
  headRefOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
21510
- createdAt: external_exports.string(),
21511
- latestReviews: external_exports.array(external_exports.object({ state: external_exports.string() }))
21511
+ createdAt: external_exports.string()
21512
21512
  });
21513
21513
  var ghPullRequestStatusSchema = external_exports.object({
21514
+ id: external_exports.string().min(1),
21514
21515
  state: external_exports.enum(["OPEN", "CLOSED", "MERGED"]),
21515
- isDraft: external_exports.boolean(),
21516
- latestReviews: external_exports.array(external_exports.object({ state: external_exports.string() }))
21516
+ isDraft: external_exports.boolean()
21517
21517
  });
21518
- function approvalCount(latestReviews) {
21519
- return latestReviews.filter((review) => review.state === "APPROVED").length;
21520
- }
21518
+ var ghOpinionatedReviewsSchema = external_exports.object({
21519
+ data: external_exports.object({
21520
+ node: external_exports.object({
21521
+ latestOpinionatedReviews: external_exports.object({
21522
+ nodes: external_exports.array(external_exports.object({ state: external_exports.string() })),
21523
+ pageInfo: external_exports.object({
21524
+ hasNextPage: external_exports.boolean(),
21525
+ endCursor: external_exports.string().nullable()
21526
+ })
21527
+ })
21528
+ }).nullable()
21529
+ })
21530
+ });
21531
+ var opinionatedReviewsQuery = `query PullRequestOpinionatedReviews($id: ID!, $after: String) {
21532
+ node(id: $id) {
21533
+ ... on PullRequest {
21534
+ latestOpinionatedReviews(first: 100, after: $after) {
21535
+ nodes { state }
21536
+ pageInfo { hasNextPage endCursor }
21537
+ }
21538
+ }
21539
+ }
21540
+ }`;
21521
21541
  function parsePullRequestUrl(url2) {
21522
21542
  const match2 = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/.*)?$/.exec(url2);
21523
21543
  if (!match2?.[1] || !match2[2] || !match2[3]) {
@@ -21547,12 +21567,80 @@ var GitHubClient = class {
21547
21567
  });
21548
21568
  }
21549
21569
  }
21570
+ async getApprovalCount(pullRequestId) {
21571
+ let approvalCount = 0;
21572
+ let after = null;
21573
+ const seenCursors = /* @__PURE__ */ new Set();
21574
+ let hasNextPage = true;
21575
+ while (hasNextPage) {
21576
+ const args = [
21577
+ "api",
21578
+ "graphql",
21579
+ "--hostname",
21580
+ "github.com",
21581
+ "-f",
21582
+ `query=${opinionatedReviewsQuery}`,
21583
+ "-f",
21584
+ `id=${pullRequestId}`
21585
+ ];
21586
+ if (after !== null) args.push("-f", `after=${after}`);
21587
+ let output;
21588
+ try {
21589
+ const result = await this.processRunner("gh", args, { timeoutMs: 6e4 });
21590
+ output = result.stdout.toString("utf8").trimEnd();
21591
+ } catch (error51) {
21592
+ if (error51 instanceof RvwError && error51.code === "PROCESS_FAILED") {
21593
+ throw new RvwError(
21594
+ "GITHUB_ERROR",
21595
+ "Pull Request\u306EApprove\u6570\u3092GitHub\u304B\u3089\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
21596
+ {
21597
+ cause: error51,
21598
+ details: error51.details,
21599
+ suggestions: ["PR\u306E\u95B2\u89A7\u6A29\u9650\u3068gh\u8A8D\u8A3C\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
21600
+ }
21601
+ );
21602
+ }
21603
+ throw error51;
21604
+ }
21605
+ let decoded;
21606
+ try {
21607
+ decoded = JSON.parse(output);
21608
+ } catch (error51) {
21609
+ throw new RvwError("GITHUB_ERROR", "GitHub CLI\u306EPull Request review\u5FDC\u7B54\u304C\u4E0D\u6B63\u3067\u3059\u3002", {
21610
+ cause: error51
21611
+ });
21612
+ }
21613
+ const parsed = ghOpinionatedReviewsSchema.safeParse(decoded);
21614
+ if (!parsed.success || parsed.data.data.node === null) {
21615
+ throw new RvwError("GITHUB_ERROR", "GitHub CLI\u306EPull Request review\u5FDC\u7B54\u304C\u4E0D\u6B63\u3067\u3059\u3002", {
21616
+ details: parsed.success ? { pullRequestId } : parsed.error.flatten()
21617
+ });
21618
+ }
21619
+ const reviews = parsed.data.data.node.latestOpinionatedReviews;
21620
+ approvalCount += reviews.nodes.filter((review) => review.state === "APPROVED").length;
21621
+ hasNextPage = reviews.pageInfo.hasNextPage;
21622
+ if (!hasNextPage) break;
21623
+ const nextCursor = reviews.pageInfo.endCursor;
21624
+ if (nextCursor === null || seenCursors.has(nextCursor)) {
21625
+ throw new RvwError(
21626
+ "GITHUB_ERROR",
21627
+ "GitHub CLI\u306EPull Request review pagination\u5FDC\u7B54\u304C\u4E0D\u6B63\u3067\u3059\u3002",
21628
+ {
21629
+ details: { pullRequestId }
21630
+ }
21631
+ );
21632
+ }
21633
+ seenCursors.add(nextCursor);
21634
+ after = nextCursor;
21635
+ }
21636
+ return approvalCount;
21637
+ }
21550
21638
  async getPullRequestStatus(reference) {
21551
21639
  let output;
21552
21640
  try {
21553
21641
  const result = await this.processRunner(
21554
21642
  "gh",
21555
- ["pr", "view", reference, "--json", "state,isDraft,latestReviews"],
21643
+ ["pr", "view", reference, "--json", "id,state,isDraft"],
21556
21644
  { timeoutMs: 6e4 }
21557
21645
  );
21558
21646
  output = result.stdout.toString("utf8").trimEnd();
@@ -21583,7 +21671,7 @@ var GitHubClient = class {
21583
21671
  return {
21584
21672
  state: parsed.data.state,
21585
21673
  isDraft: parsed.data.isDraft,
21586
- approvalCount: approvalCount(parsed.data.latestReviews)
21674
+ approvalCount: await this.getApprovalCount(parsed.data.id)
21587
21675
  };
21588
21676
  }
21589
21677
  async getPullRequestStatuses(references) {
@@ -21625,6 +21713,7 @@ var GitHubClient = class {
21625
21713
  async getPullRequest(reference, cwd, options = {}) {
21626
21714
  await this.assertAuthenticated();
21627
21715
  const fields = [
21716
+ "id",
21628
21717
  "author",
21629
21718
  "number",
21630
21719
  "url",
@@ -21639,15 +21728,15 @@ var GitHubClient = class {
21639
21728
  "headRefName",
21640
21729
  "headRefOid",
21641
21730
  "headRepository",
21642
- "headRepositoryOwner",
21643
- "latestReviews"
21731
+ "headRepositoryOwner"
21644
21732
  ].join(",");
21645
21733
  const args = ["pr", "view"];
21646
21734
  if (reference !== void 0 && reference.length > 0) args.push(reference);
21647
21735
  args.push("--json", fields);
21648
21736
  let output;
21649
21737
  try {
21650
- output = await runText("gh", args, { cwd, timeoutMs: 6e4 });
21738
+ const result = await this.processRunner("gh", args, { cwd, timeoutMs: 6e4 });
21739
+ output = result.stdout.toString("utf8").trimEnd();
21651
21740
  } catch (error51) {
21652
21741
  if (error51 instanceof RvwError && error51.code === "PROCESS_FAILED") {
21653
21742
  throw new RvwError("GITHUB_ERROR", "Pull Request\u60C5\u5831\u3092GitHub\u304B\u3089\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002", {
@@ -21690,7 +21779,7 @@ var GitHubClient = class {
21690
21779
  updatedAt: parsed.data.updatedAt,
21691
21780
  state: parsed.data.state,
21692
21781
  isDraft: parsed.data.isDraft,
21693
- approvalCount: approvalCount(parsed.data.latestReviews)
21782
+ approvalCount: await this.getApprovalCount(parsed.data.id)
21694
21783
  };
21695
21784
  }
21696
21785
  async getAttachment(absoluteUrl) {
@@ -44315,6 +44404,23 @@ var RvwService = class {
44315
44404
  this.createCommentPlacementRequestContext()
44316
44405
  );
44317
44406
  }
44407
+ async placeCodeReferenceAtCommit(pullRequestId, sourceOid, reference, destinationOid) {
44408
+ assertCodeReferencePath(reference.path);
44409
+ return await this.placeCommentAtCommit(
44410
+ {
44411
+ pullRequestId,
44412
+ target: {
44413
+ kind: "document",
44414
+ documentKind: "repository-file",
44415
+ sourceOid,
44416
+ path: reference.path,
44417
+ startLine: reference.startLine,
44418
+ endLine: reference.endLine
44419
+ }
44420
+ },
44421
+ destinationOid
44422
+ );
44423
+ }
44318
44424
  async resolveCommentPlacements(pullRequestId, commentIds, destinations, expectedPullRequestContentFingerprint) {
44319
44425
  const pullRequest = this.getPullRequest(pullRequestId);
44320
44426
  const contentFingerprint = this.assertPullRequestContentFingerprint(
@@ -49477,6 +49583,11 @@ var resolveCommentPlacementsSchema = external_exports.object({
49477
49583
  ])
49478
49584
  ).min(1).max(4)
49479
49585
  });
49586
+ var resolveCodeReferencePlacementSchema = external_exports.object({
49587
+ sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
49588
+ destinationOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
49589
+ reference: codeReferenceInputSchema
49590
+ });
49480
49591
 
49481
49592
  // src/server/app.ts
49482
49593
  var oidSchema = external_exports.string().regex(GIT_OBJECT_ID_PATTERN);
@@ -49954,6 +50065,18 @@ function createApp(service, options) {
49954
50065
  )
49955
50066
  });
49956
50067
  });
50068
+ app.post("/api/pull-requests/:pullRequestId/code-reference-placement", async (context) => {
50069
+ const input = resolveCodeReferencePlacementSchema.parse(await context.req.json());
50070
+ return context.json({
50071
+ ok: true,
50072
+ placement: await service.placeCodeReferenceAtCommit(
50073
+ context.req.param("pullRequestId"),
50074
+ input.sourceOid,
50075
+ input.reference,
50076
+ input.destinationOid
50077
+ )
50078
+ });
50079
+ });
49957
50080
  app.get("/api/comments/:id/placement", async (context) => {
49958
50081
  const comment3 = service.database.getComment(context.req.param("id"));
49959
50082
  if (!comment3)