@a9n-shoji/rvw 0.10.0 → 0.12.0

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,25 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.12.0] - 2026-10-05
9
+
10
+ ### Added
11
+
12
+ - 複数のPull Requestを横断するコメント一覧を追加。コメントの本文、対象PR、解決状態をまとめて確認できます。
13
+ - コメント一覧から返信し、対象のコード位置へ移動できるようにしました。
14
+
15
+ ## [0.11.0] - 2026-10-02
16
+
17
+ ### Added
18
+
19
+ - PR一覧にアーカイブ・解除と、既定ONの「アーカイブ済みを非表示」を追加。Closed / Mergedの絞り込みと独立して利用できます。
20
+ - 独自Skillから操作できる`rvw pr archive`、`rvw pr unarchive`、`rvw pr list`と対応APIを追加。Machine protocolは6を維持し、`pullRequest.archive`と`pullRequest.list`を追加。
21
+
22
+ ### Changed
23
+
24
+ - アーカイブ状態はローカルDBへ保存し、同期・reset後も維持。アーカイブ済みでも直接閲覧・コメントを継続できます。同梱Skillによる自動アーカイブは行いません。
25
+ - 最終ページが空になった場合の自動補正はブラウザ履歴を置き換え、「戻る」がループしないようにしました。
26
+
8
27
  ## [0.10.0] - 2026-09-23
9
28
 
10
29
  ### Added
package/README.md CHANGED
@@ -71,6 +71,14 @@ PR全体の流れを把握するために、Agentが生成した解説(Walkthr
71
71
 
72
72
  ---
73
73
 
74
+ ## PRを横断してコメントを読む
75
+
76
+ 入口の`Pull Requests / Comments`でPR単位とコメント単位を切り替えられます。CommentsではPRごとに
77
+ 全スレッドの本文・返信を展開表示し、その場で返信、解決/再開できます。PR/スレッド/全体の折りたたみ、
78
+ 状態・リポジトリ・PRの絞り込み、返信本文も含む検索に対応します。「PRで開く」でコメントの対象箇所へ移動し、
79
+ 戻ると一覧の位置と下書きを保ちます。外部からの返信は自動反映され、並びや表示対象の変更は
80
+ 「新しい更新を反映」で適用します。
81
+
74
82
  ## Agent連携(Codex / Claude Code)
75
83
 
76
84
  ローカルの Agent に Skill を登録することで、PRの構成図生成や、行コメントに対する調査・返信を自動化できます。
@@ -154,6 +162,11 @@ default / namespace / package import、re-export、tsconfig paths、完全なmod
154
162
 
155
163
  ---
156
164
 
165
+ ## 見終わったPRを整理する
166
+
167
+ PR一覧の各行からアーカイブ・解除できます。アーカイブ済みは既定で非表示になり、チェックを外すと再表示できます。
168
+ PRの閲覧やコメントは引き続き利用できます。[操作と独自Skillでの自動化](https://github.com/a9n-shoji/rvw/blob/main/docs/usage.md#pr一覧のアーカイブ)を参照してください。
169
+
157
170
  ## ドキュメント
158
171
 
159
172
  ソースから `pnpm demo` を実行すると、デモ環境(認証・Agent不要)をローカルで試せます。
package/dist/cli.mjs CHANGED
@@ -18753,7 +18753,7 @@ function parseWalkthroughUri(uri) {
18753
18753
  }
18754
18754
 
18755
18755
  // src/shared/constants.ts
18756
- var APP_VERSION = "0.10.0";
18756
+ var APP_VERSION = "0.12.0";
18757
18757
  var PROTOCOL_VERSION = 6;
18758
18758
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
18759
18759
  var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
@@ -19014,6 +19014,7 @@ function mapPullRequest(row) {
19014
19014
  githubState: nullableGitHubPullRequestState(row, "github_state"),
19015
19015
  githubIsDraft: nullableBoolean(row, "github_is_draft"),
19016
19016
  githubApprovalCount: nullableNumber(row, "github_approval_count"),
19017
+ archivedAt: nullableString(row, "archived_at"),
19017
19018
  fetchedAt: stringValue(row, "fetched_at"),
19018
19019
  createdAt: stringValue(row, "created_at"),
19019
19020
  updatedAt: stringValue(row, "updated_at")
@@ -19566,7 +19567,7 @@ var RvwDatabase = class {
19566
19567
  "SELECT * FROM pull_requests WHERE github_state = 'OPEN' OR github_state IS NULL ORDER BY updated_at DESC"
19567
19568
  ).all().map(mapPullRequest);
19568
19569
  }
19569
- listPullRequestSummaries(offset, limit, hideClosedOrMerged = true) {
19570
+ listPullRequestSummaries(offset, limit, hideClosedOrMerged = true, hideArchived = true) {
19570
19571
  const hideClosedOrMergedValue = hideClosedOrMerged ? 1 : 0;
19571
19572
  const rows = this.database.prepare(
19572
19573
  `WITH page AS (
@@ -19580,9 +19581,10 @@ var RvwDatabase = class {
19580
19581
  github_updated_at,
19581
19582
  github_state,
19582
19583
  github_is_draft,
19583
- github_approval_count
19584
+ github_approval_count,
19585
+ archived_at
19584
19586
  FROM pull_requests
19585
- WHERE ? = 0 OR github_state IS NULL OR github_state = 'OPEN'
19587
+ WHERE (? = 0 OR github_state IS NULL OR github_state = 'OPEN') AND (? = 0 OR archived_at IS NULL)
19586
19588
  ORDER BY github_updated_at DESC, id DESC
19587
19589
  LIMIT ? OFFSET ?
19588
19590
  ), comment_counts AS (
@@ -19615,6 +19617,7 @@ var RvwDatabase = class {
19615
19617
  pr.github_state,
19616
19618
  pr.github_is_draft,
19617
19619
  pr.github_approval_count,
19620
+ pr.archived_at,
19618
19621
  COALESCE(comment_counts.unresolved_count, 0) AS unresolved_comment_count,
19619
19622
  COALESCE(comment_counts.resolved_count, 0) AS resolved_comment_count,
19620
19623
  COALESCE(walkthrough_counts.walkthrough_count, 0) AS walkthrough_count,
@@ -19624,10 +19627,10 @@ var RvwDatabase = class {
19624
19627
  LEFT JOIN walkthrough_counts ON walkthrough_counts.pull_request_id = pr.id
19625
19628
  LEFT JOIN structure_counts ON structure_counts.pull_request_id = pr.id
19626
19629
  ORDER BY pr.github_updated_at DESC, pr.id DESC`
19627
- ).all(hideClosedOrMergedValue, limit, offset);
19630
+ ).all(hideClosedOrMergedValue, hideArchived ? 1 : 0, limit, offset);
19628
19631
  const totalRow = this.database.prepare(
19629
- "SELECT COUNT(*) AS total FROM pull_requests WHERE ? = 0 OR github_state IS NULL OR github_state = 'OPEN'"
19630
- ).get(hideClosedOrMergedValue);
19632
+ "SELECT COUNT(*) AS total FROM pull_requests WHERE (? = 0 OR github_state IS NULL OR github_state = 'OPEN') AND (? = 0 OR archived_at IS NULL)"
19633
+ ).get(hideClosedOrMergedValue, hideArchived ? 1 : 0);
19631
19634
  if (!totalRow) throw new RvwError("DATABASE_ERROR", "Pull Request\u4EF6\u6570\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3002");
19632
19635
  return {
19633
19636
  items: rows.map((row) => ({
@@ -19641,6 +19644,7 @@ var RvwDatabase = class {
19641
19644
  githubState: nullableGitHubPullRequestState(row, "github_state"),
19642
19645
  githubIsDraft: nullableBoolean(row, "github_is_draft"),
19643
19646
  githubApprovalCount: nullableNumber(row, "github_approval_count"),
19647
+ archivedAt: nullableString(row, "archived_at"),
19644
19648
  unresolvedCommentCount: numberValue(row, "unresolved_comment_count"),
19645
19649
  resolvedCommentCount: numberValue(row, "resolved_comment_count"),
19646
19650
  walkthroughCount: numberValue(row, "walkthrough_count"),
@@ -19649,6 +19653,18 @@ var RvwDatabase = class {
19649
19653
  total: numberValue(totalRow, "total")
19650
19654
  };
19651
19655
  }
19656
+ setPullRequestArchived(id, archived) {
19657
+ return this.immediateTransaction(() => {
19658
+ const current = this.getPullRequest(id);
19659
+ if (!current)
19660
+ throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
19661
+ if (current.archivedAt !== null === archived) return current;
19662
+ const archivedAt = archived ? (/* @__PURE__ */ new Date()).toISOString() : null;
19663
+ this.database.prepare("UPDATE pull_requests SET archived_at = ? WHERE id = ?").run(archivedAt, id);
19664
+ this.incrementDomainRevisions(["pullRequests"]);
19665
+ return { ...current, archivedAt };
19666
+ });
19667
+ }
19652
19668
  updatePullRequestGitHubStatuses(updates) {
19653
19669
  if (updates.length === 0) return;
19654
19670
  this.immediateTransaction(() => {
@@ -20412,6 +20428,70 @@ var RvwDatabase = class {
20412
20428
  ).get(id);
20413
20429
  return row ? this.mapComment(row) : null;
20414
20430
  }
20431
+ listCommentFeed(input) {
20432
+ const parameters = [
20433
+ input.status,
20434
+ input.status,
20435
+ input.hideClosedOrMerged ? 1 : 0,
20436
+ input.repository,
20437
+ input.repository,
20438
+ input.pullRequestId,
20439
+ input.pullRequestId,
20440
+ input.search,
20441
+ input.search
20442
+ ];
20443
+ const matching = `WITH matching AS (
20444
+ SELECT c.id, c.pull_request_id,
20445
+ (SELECT MAX(p.created_at) FROM comment_posts p WHERE p.comment_id = c.id) AS posted_at
20446
+ FROM comments c JOIN pull_requests pr ON pr.id = c.pull_request_id
20447
+ WHERE (? = 'all' OR CASE WHEN ? = 'resolved' THEN c.resolved_at IS NOT NULL ELSE c.resolved_at IS NULL END)
20448
+ AND (? = 0 OR pr.github_state IS NULL OR pr.github_state = 'OPEN')
20449
+ AND (? = '' OR pr.owner || '/' || pr.repository = ?)
20450
+ AND (? = '' OR pr.id = ?)
20451
+ AND (? = '' OR EXISTS (SELECT 1 FROM comment_posts p WHERE p.comment_id = c.id
20452
+ AND instr(lower(p.body), lower(?)) > 0))
20453
+ )`;
20454
+ const totals = this.database.prepare(
20455
+ `${matching}
20456
+ SELECT COUNT(*) AS comments, COUNT(DISTINCT pull_request_id) AS groups FROM matching`
20457
+ ).get(...parameters);
20458
+ const rows = this.database.prepare(
20459
+ `${matching}, page AS (
20460
+ SELECT pull_request_id, MAX(posted_at) AS posted_at FROM matching
20461
+ GROUP BY pull_request_id ORDER BY posted_at DESC, pull_request_id DESC LIMIT ?
20462
+ ) SELECT m.id, m.pull_request_id FROM matching m JOIN page p ON p.pull_request_id = m.pull_request_id
20463
+ ORDER BY p.posted_at DESC, p.pull_request_id DESC, m.posted_at DESC, m.id DESC`
20464
+ ).all(...parameters, input.limit);
20465
+ const pullRequests = this.database.prepare(
20466
+ `SELECT id, owner, repository, number, latest_title, github_state, github_is_draft
20467
+ FROM pull_requests ORDER BY owner, repository, number DESC`
20468
+ ).all().map((row) => ({
20469
+ id: stringValue(row, "id"),
20470
+ owner: stringValue(row, "owner"),
20471
+ repository: stringValue(row, "repository"),
20472
+ number: numberValue(row, "number"),
20473
+ title: stringValue(row, "latest_title"),
20474
+ githubState: nullableString(row, "github_state"),
20475
+ githubIsDraft: row.github_is_draft === null ? null : row.github_is_draft === 1
20476
+ }));
20477
+ const byId = new Map(pullRequests.map((pr) => [pr.id, pr]));
20478
+ const groups = [];
20479
+ for (const row of rows) {
20480
+ const id = stringValue(row, "pull_request_id");
20481
+ let group = groups.at(-1);
20482
+ if (group?.pullRequest.id !== id) {
20483
+ group = { pullRequest: byId.get(id), commentIds: [] };
20484
+ groups.push(group);
20485
+ }
20486
+ group.commentIds.push(stringValue(row, "id"));
20487
+ }
20488
+ return {
20489
+ groups,
20490
+ pullRequests,
20491
+ totalGroups: numberValue(totals, "groups"),
20492
+ totalComments: numberValue(totals, "comments")
20493
+ };
20494
+ }
20415
20495
  listComments(pullRequestId, resolved) {
20416
20496
  const where = resolved === void 0 ? "" : resolved ? " AND c.resolved_at IS NOT NULL" : " AND c.resolved_at IS NULL";
20417
20497
  return this.database.prepare(
@@ -43057,6 +43137,9 @@ var RvwService = class {
43057
43137
  throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
43058
43138
  return pullRequest;
43059
43139
  }
43140
+ setPullRequestArchived(id, archived) {
43141
+ return this.database.setPullRequestArchived(id, archived);
43142
+ }
43060
43143
  listPullRequests(input) {
43061
43144
  const offset = input.offset ?? 0;
43062
43145
  const limit = input.limit ?? DEFAULT_PULL_REQUEST_LIST_LIMIT;
@@ -43070,7 +43153,12 @@ var RvwService = class {
43070
43153
  `limit\u306F1\u4EE5\u4E0A${MAX_PULL_REQUEST_LIST_LIMIT}\u4EE5\u4E0B\u306E\u6574\u6570\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43071
43154
  );
43072
43155
  }
43073
- const page = this.database.listPullRequestSummaries(offset, limit, hideClosedOrMerged);
43156
+ const page = this.database.listPullRequestSummaries(
43157
+ offset,
43158
+ limit,
43159
+ hideClosedOrMerged,
43160
+ input.hideArchived ?? true
43161
+ );
43074
43162
  const returned = page.items.length;
43075
43163
  const hasMore = offset + returned < page.total;
43076
43164
  return {
@@ -49938,6 +50026,13 @@ var structurePublishInputSchema = external_exports.object({
49938
50026
  }).strict().superRefine(refineStructureContent);
49939
50027
  var agentCommandInputSchemas = {
49940
50028
  doctor: external_exports.object({ cwd: nonEmptyString }).strict(),
50029
+ "pr.list": external_exports.object({
50030
+ offset: external_exports.number().int().min(0).default(0),
50031
+ limit: external_exports.number().int().min(1).max(MAX_PULL_REQUEST_LIST_LIMIT).default(DEFAULT_PULL_REQUEST_LIST_LIMIT),
50032
+ hideClosedOrMerged: external_exports.boolean().default(true),
50033
+ hideArchived: external_exports.boolean().default(true)
50034
+ }).strict(),
50035
+ "pr.archive": external_exports.object({ reference: nonEmptyString, archived: external_exports.boolean() }).strict(),
49941
50036
  "pr.refresh": external_exports.object({ reference: nonEmptyString }).strict(),
49942
50037
  "pr.sync": pullRequestSyncInputSchema.extend({
49943
50038
  repositoryPath: nonEmptyString.optional(),
@@ -50013,6 +50108,7 @@ var openPullRequestSchema = external_exports.object({
50013
50108
  reference: external_exports.string().min(1).optional(),
50014
50109
  cwd: external_exports.string().min(1)
50015
50110
  });
50111
+ var pullRequestArchiveSchema = external_exports.object({ archived: external_exports.boolean() }).strict();
50016
50112
  var resetSchema = external_exports.object({ yes: external_exports.boolean() });
50017
50113
  var structureDeleteSchema = external_exports.object({ expectedUpdatedAt: external_exports.string().min(1).max(100) });
50018
50114
  var viewerIdSchema = external_exports.uuid();
@@ -50021,6 +50117,7 @@ var themePreferenceSchema = external_exports.object({ themePreference: external_
50021
50117
  var pullRequestListQuerySchema = external_exports.object({
50022
50118
  offset: external_exports.coerce.number().int().min(0).default(0),
50023
50119
  limit: external_exports.coerce.number().int().min(1).max(MAX_PULL_REQUEST_LIST_LIMIT).default(DEFAULT_PULL_REQUEST_LIST_LIMIT),
50120
+ hideArchived: external_exports.enum(["true", "false"]).default("true").transform((value) => value === "true"),
50024
50121
  hideClosedOrMerged: external_exports.enum(["true", "false"]).default("true").transform((value) => value === "true")
50025
50122
  });
50026
50123
  var createCommentSchema = external_exports.object({
@@ -50229,10 +50326,29 @@ function createApp(service, options) {
50229
50326
  const query = pullRequestListQuerySchema.parse({
50230
50327
  offset: context.req.query("offset"),
50231
50328
  limit: context.req.query("limit"),
50329
+ hideArchived: context.req.query("hideArchived"),
50232
50330
  hideClosedOrMerged: context.req.query("hideClosedOrMerged")
50233
50331
  });
50234
50332
  return context.json({ ok: true, ...service.listPullRequests(query) });
50235
50333
  });
50334
+ app.patch("/api/pull-requests/:id/archive", async (context) => {
50335
+ const input = pullRequestArchiveSchema.parse(await context.req.json());
50336
+ return context.json({
50337
+ ok: true,
50338
+ pullRequest: service.setPullRequestArchived(context.req.param("id"), input.archived)
50339
+ });
50340
+ });
50341
+ app.get("/api/comment-feed", (context) => {
50342
+ const input = external_exports.object({
50343
+ status: external_exports.enum(["unresolved", "resolved", "all"]).default("unresolved"),
50344
+ hideClosedOrMerged: external_exports.enum(["true", "false"]).default("true").transform((value) => value === "true"),
50345
+ repository: external_exports.string().max(300).default(""),
50346
+ pullRequestId: external_exports.union([external_exports.uuid(), external_exports.literal("")]).default(""),
50347
+ search: external_exports.string().max(500).default(""),
50348
+ limit: external_exports.coerce.number().int().min(1).max(1e3).default(20)
50349
+ }).parse(context.req.query());
50350
+ return context.json({ ok: true, ...service.database.listCommentFeed(input) });
50351
+ });
50236
50352
  app.post(
50237
50353
  "/api/pull-requests/refresh-statuses",
50238
50354
  async (context) => context.json({ ok: true, ...await service.refreshPullRequestStatuses() })
@@ -51019,6 +51135,16 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
51019
51135
  const input = parseOperationInput("doctor", request.input);
51020
51136
  return await service.doctor(input.cwd);
51021
51137
  }
51138
+ case "pr.list": {
51139
+ return service.listPullRequests(parseOperationInput("pr.list", request.input));
51140
+ }
51141
+ case "pr.archive": {
51142
+ const input = parseOperationInput("pr.archive", request.input);
51143
+ return service.setPullRequestArchived(
51144
+ service.resolveStoredPullRequest(input.reference).id,
51145
+ input.archived
51146
+ );
51147
+ }
51022
51148
  case "pr.refresh": {
51023
51149
  const input = parseOperationInput("pr.refresh", request.input);
51024
51150
  return await service.refreshByReference(input.reference);
@@ -52840,6 +52966,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
52840
52966
  "comment.resolve",
52841
52967
  "comment.reopen",
52842
52968
  "pullRequest.sync",
52969
+ "pullRequest.list",
52970
+ "pullRequest.archive",
52843
52971
  "structure.list",
52844
52972
  "structure.read",
52845
52973
  "structure.preview",
@@ -52906,6 +53034,47 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
52906
53034
  }
52907
53035
  });
52908
53036
  const pr = program2.command("pr").description("Pull Request\u72B6\u614B\u3092\u7BA1\u7406");
53037
+ pr.command("list").option("--json", "JSON\u3067\u51FA\u529B").option("--offset <offset>", "\u958B\u59CB\u4F4D\u7F6E", "0").option("--limit <limit>", "\u53D6\u5F97\u4EF6\u6570\uFF08\u6700\u5927100\uFF09", "50").option("--include-archived", "\u30A2\u30FC\u30AB\u30A4\u30D6\u6E08\u307F\u3082\u542B\u3081\u308B").option("--include-closed", "Closed / Merged\u3082\u542B\u3081\u308B").action(
53038
+ async (options) => {
53039
+ const input = {
53040
+ offset: Number(options.offset),
53041
+ limit: Number(options.limit),
53042
+ hideArchived: !options.includeArchived,
53043
+ hideClosedOrMerged: !options.includeClosed
53044
+ };
53045
+ const result = await callService(
53046
+ "pr.list",
53047
+ input,
53048
+ () => getRuntime().service.listPullRequests(input)
53049
+ );
53050
+ writeOutput(
53051
+ options,
53052
+ { ok: true, ...result },
53053
+ result.items.map(
53054
+ (item) => `${item.owner}/${item.repository}#${item.number} ${item.title}${item.archivedAt ? " [archived]" : ""}`
53055
+ ).join("\n") || "\u8A72\u5F53\u3059\u308BPull Request\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
53056
+ );
53057
+ }
53058
+ );
53059
+ for (const [command, archived] of [
53060
+ ["archive", true],
53061
+ ["unarchive", false]
53062
+ ]) {
53063
+ pr.command(command).argument("<pull-request>", "\u767B\u9332\u6E08\u307FPR URL\u307E\u305F\u306F\u756A\u53F7").option("--json", "JSON\u3067\u51FA\u529B").action(async (reference, options) => {
53064
+ const pullRequest = await callService("pr.archive", { reference, archived }, () => {
53065
+ const service = getRuntime().service;
53066
+ return service.setPullRequestArchived(
53067
+ service.resolveStoredPullRequest(reference).id,
53068
+ archived
53069
+ );
53070
+ });
53071
+ writeOutput(
53072
+ options,
53073
+ { ok: true, pullRequest },
53074
+ archived ? "\u30A2\u30FC\u30AB\u30A4\u30D6\u3057\u307E\u3057\u305F\u3002" : "\u30A2\u30FC\u30AB\u30A4\u30D6\u3092\u89E3\u9664\u3057\u307E\u3057\u305F\u3002"
53075
+ );
53076
+ });
53077
+ }
52909
53078
  pr.command("refresh").argument("<pull-request>", "\u767B\u9332\u6E08\u307FPR URL\u307E\u305F\u306F\u756A\u53F7").option("--json", "JSON\u3067\u51FA\u529B").action(async (reference, options) => {
52910
53079
  const result = await callService(
52911
53080
  "pr.refresh",