@a9n-shoji/rvw 0.2.1 → 0.2.3
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 +29 -0
- package/README.md +24 -4
- package/dist/cli.mjs +188 -19
- package/dist/cli.mjs.map +4 -4
- package/dist/web/assets/DocumentViewer-B4LdSL1l.js +1560 -0
- package/dist/web/assets/{MarkdownTable-DbDc9VoP.js → MarkdownTable-CdbKPft2.js} +1 -1
- package/dist/web/assets/WalkthroughViewer-D1LlSQw2.js +1 -0
- package/dist/web/assets/{abnfDiagram-N423BO3Z-fQDP4qYL.js → abnfDiagram-N423BO3Z-CE1nZSC9.js} +1 -1
- package/dist/web/assets/architecture-TIHT7OUA-edLV2Zq-.js +1 -0
- package/dist/web/assets/{architectureDiagram-T3A2C74G-D3Aon3Nd.js → architectureDiagram-T3A2C74G-B88qYFUo.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-DS1DcAqy.js → blockDiagram-VBNYF7ZC-BaX0rhbb.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BHQWz7l2.js → c4Diagram-5PPSVZJV-C1ppBaEC.js} +1 -1
- package/dist/web/assets/channel-J8FHSJDG.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-CaahxDVT.js → chunk-2GRJ4B5K-D2TTocfh.js} +1 -1
- package/dist/web/assets/{chunk-4I5QYGJK-Bat5hdXD.js → chunk-4I5QYGJK-ikT25rPN.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-jyonN41f.js → chunk-5RXB4S5H-CGlPaulj.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-BBo9TYzL.js → chunk-6Q2QTUOP-BuuMeCo2.js} +1 -1
- package/dist/web/assets/{chunk-7Z6QIM7H-CGSwsKhC.js → chunk-7Z6QIM7H-DT3BAZ4L.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-Cuca3bAY.js → chunk-GF5L2VYU-mIdlxzfr.js} +1 -1
- package/dist/web/assets/{chunk-I66GZJ75-DRdqLSyH.js → chunk-I66GZJ75-CGl1hx4J.js} +1 -1
- package/dist/web/assets/{chunk-JQJVKLGR-8xhLkhs2.js → chunk-JQJVKLGR-BLG6gAfx.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-oOV-3e5-.js → chunk-KBJHAD2P-D_MPzBJx.js} +1 -1
- package/dist/web/assets/{chunk-NSK5VX7P-CXit1l3q.js → chunk-NSK5VX7P-CiYiU8wP.js} +1 -1
- package/dist/web/assets/{chunk-QR6OTTB3-DcdCU-01.js → chunk-QR6OTTB3-BK8Tnvcc.js} +1 -1
- package/dist/web/assets/{chunk-UBXNYLIW-i72Hgljr.js → chunk-UBXNYLIW-BuzttTFk.js} +1 -1
- package/dist/web/assets/{chunk-W5SLKNZC-DetAQo7M.js → chunk-W5SLKNZC-UidmFHe2.js} +1 -1
- package/dist/web/assets/{chunk-WRU74C26-DDUvJFNs.js → chunk-WRU74C26-nox7WFnh.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CmTWSnH0.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CmTWSnH0.js +1 -0
- package/dist/web/assets/{cynefin-VYW2F7L2-fd-v6ani.js → cynefin-VYW2F7L2-wDMibS5s.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-DRsVpAC3.js → cynefinDiagram-MW4NZA55-CBTrdvOE.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-BnRSs_fe.js → dagre-VZM6K2ZE-Bbgn4Rr7.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-D1g0PR4y.js → diagram-7IWD3JNH-Bm9AuXb9.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-DuDKsQLp.js → diagram-B4RE2ZJO-D3lZ-r-H.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-COpzgGe7.js → diagram-LBJQPF4R-D-GaynX7.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-DR2vRbPE.js → diagram-Q27KOJAE-BeZRiu1d.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-hs4VGPP7.js → diagram-UB23O5K3-C3GP7iMe.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-B_4NlK9q.js → ebnfDiagram-BXEA7PRR-BJWg0NVU.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-JyoZ3U-o.js → erDiagram-JOGREHBK-XPmhUeB_.js} +1 -1
- package/dist/web/assets/eventmodeling-45OFAUF4-C3c4qddw.js +1 -0
- package/dist/web/assets/flowDiagram-UKHOOZJN-DMA-a1zf.js +1 -0
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-f37I0Twm.js → ganttDiagram-PKOTCBZU-CL-xEo-M.js} +1 -1
- package/dist/web/assets/{gitGraph-TEB2WS4Q-B_QWjr7j.js → gitGraph-TEB2WS4Q-BVRyuIx3.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-AZsaC0hu.js → gitGraphDiagram-DS77QQ5N-BVNIEupW.js} +1 -1
- package/dist/web/assets/index-D8GJBw23.js +384 -0
- package/dist/web/assets/{index-kK3hngnz.css → index-DMD2vvQW.css} +1 -1
- package/dist/web/assets/{info-DKCQHKI2-BRfisbEI.js → info-DKCQHKI2-BKBUVpfc.js} +1 -1
- package/dist/web/assets/{infoDiagram-6WML65LV-CaIb64N5.js → infoDiagram-6WML65LV-_dTFnzSE.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-BoJrcTja.js → ishikawaDiagram-WSZJBQD7-DIRgD4hO.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-CRR1dZy8.js → journeyDiagram-NVQOT4AX-bp7nZrfR.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-D5skDuP4.js → kanban-definition-27J2QSJJ-C6mZIo8S.js} +1 -1
- package/dist/web/assets/{line-BKKx0vSh.js → line-DWTym7eJ.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-DvZnIth0.js → mermaid-parser.core-CbecsNA1.js} +3 -3
- package/dist/web/assets/{mermaid.core-hykiOF_o.js → mermaid.core-CdRuLyYI.js} +4 -4
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BN2LQ4Yn.js → mindmap-definition-FAOFIHXS-CU2P1z-9.js} +1 -1
- package/dist/web/assets/{packet-7NZHBO7P-CS5I0NlV.js → packet-7NZHBO7P-CZsup6Od.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-WvOdhbxo.js → pegDiagram-VL7TDLO6-DTTyttpe.js} +1 -1
- package/dist/web/assets/{pie-RZYD4A2V-BwNAZrQ1.js → pie-RZYD4A2V-DgrIiWZA.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-CbyrZjm7.js → pieDiagram-7S7Q4E2Y-BBNFyPT1.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-CW9YBxAO.js → quadrantDiagram-CIZ2JOQS-ejp-Aa2l.js} +1 -1
- package/dist/web/assets/{radar-I7S5WNFK-DBoCLDNH.js → radar-I7S5WNFK-Cp_hfjrI.js} +1 -1
- package/dist/web/assets/{railroad-3IZDKUUU-BOjpYyDz.js → railroad-3IZDKUUU-Dw-oMal2.js} +1 -1
- package/dist/web/assets/railroad-abnf-AHOZXSZD-BN-ZBhE0.js +1 -0
- package/dist/web/assets/railroad-ebnf-EBAXGLYW-CG97zyI2.js +1 -0
- package/dist/web/assets/railroad-peg-LSFZ7HO6-DHLQ1evQ.js +1 -0
- package/dist/web/assets/{railroadDiagram-AXF67PYL-BdOOOq8o.js → railroadDiagram-AXF67PYL-DkBUaj6Z.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-DIYlSrTb.js → requirementDiagram-LRYGKXZP-Dff1ex_h.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-Cc1IGxp_.js → sankeyDiagram-W5VNT64P-DptSL9Bc.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-CJsPO9Z9.js → sequenceDiagram-SI44F4Z6-DEnPcs3U.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-B033gpHZ.js → stateDiagram-OKZ733FA-BwJO1MKA.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-LaSNqvWy.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-C0Tstg7e.js → swimlanes-SLNWSIFB-BosKiwZA.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-C5OBtBlv.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-BA3y-16W.js → timeline-definition-Z64GVDOM-C5isAn4X.js} +1 -1
- package/dist/web/assets/{treeView-QDETBFTQ-BUNq-JXr.js → treeView-QDETBFTQ-zEyUXRc4.js} +1 -1
- package/dist/web/assets/{treemap-6X3UGDF4-FIR_NbUo.js → treemap-6X3UGDF4-B-X0EQSE.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-IeFFjnlq.js → vennDiagram-T6HMQDX7-QMdGieCa.js} +1 -1
- package/dist/web/assets/{wardley-OPB4EBWU-DQAX4LKo.js → wardley-OPB4EBWU-BaeAF8d8.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-guGPPMAG.js → wardleyDiagram-T6FBY63Y-DTQ_uLUq.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-Cj0v359_.js → xychartDiagram-ELKLHX3M-BMdBWzib.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +17 -14
- package/skills/rvw/SKILL.md +56 -15
- package/skills/rvw-watch-comments/SKILL.md +117 -47
- package/skills/rvw-watch-comments/agents/openai.yaml +2 -2
- package/skills/rvw-watch-comments/scripts/auto-ack.mjs +0 -0
- package/skills/rvw-watch-comments/scripts/preflight.mjs +0 -0
- package/skills/rvw-watch-comments/scripts/watch-driver.mjs +223 -48
- package/skills/rvw-watch-comments/scripts/watch-state.mjs +125 -64
- package/dist/web/assets/DocumentViewer-DvrDlIpR.js +0 -1560
- package/dist/web/assets/WalkthroughViewer-aDUeKC3B.js +0 -1
- package/dist/web/assets/architecture-TIHT7OUA-CuFS9GkN.js +0 -1
- package/dist/web/assets/channel-CTWz7tRI.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-BJQv9QuO.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-BJQv9QuO.js +0 -1
- package/dist/web/assets/eventmodeling-45OFAUF4-CfUhQK5g.js +0 -1
- package/dist/web/assets/flowDiagram-UKHOOZJN-FFZ82SPa.js +0 -1
- package/dist/web/assets/index-CY_JoUL3.js +0 -384
- package/dist/web/assets/railroad-abnf-AHOZXSZD-DQhDS2pf.js +0 -1
- package/dist/web/assets/railroad-ebnf-EBAXGLYW-Dtt2kOaN.js +0 -1
- package/dist/web/assets/railroad-peg-LSFZ7HO6-nfH8zno6.js +0 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-wS01qj3t.js +0 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-DoAqfWAd.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.3] - 2026-08-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- PR本文へ貼り付けたmodern GitHub user attachment画像を、public/private共通のlocalhost proxyで表示
|
|
13
|
+
- repositoryのPNG、JPEG、GIF、WebP、AVIF、SVGをexact commit固定の全文表示とold/new Split表示で閲覧
|
|
14
|
+
- 画像ファイルのadded、deleted、renamed、画像・非画像間の変更表示とfile-level comment
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- 同じtask stateで`rvw-watch-comments`のwatch driverを複数起動できないようにし、二重監視と重複処理を防止
|
|
19
|
+
|
|
20
|
+
### Security
|
|
21
|
+
|
|
22
|
+
- GitHub attachment proxyを厳密な`https://github.com/user-attachments/assets/<uuid>`だけへ限定し、
|
|
23
|
+
同一origin request、GitHub CLIの既存認証、10 MiB上限、magic-byte判定、`nosniff`、SVG sandbox CSPを適用
|
|
24
|
+
- arbitrary external image、legacy GitHub image host、unknown binary、HTML/JSON error responseは引き続き自動取得しない
|
|
25
|
+
|
|
26
|
+
## [0.2.2] - 2026-08-21
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `rvw`と`rvw-watch-comments` Skillが具体的なcode上の結論、実装、testを投稿するとき、navigation価値のあるtyped code referenceを既定で付けるよう改善
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- `rvw-watch-comments`が同じthreadの後続replyを処理するとき、以前の回答を確認中へ戻さず、新しい確認中replyを最終回答へ更新するよう修正
|
|
35
|
+
- Markdown Previewで別commentへの返信が同期されても、入力中の未送信replyとfocusを保持するよう修正
|
|
36
|
+
|
|
8
37
|
## [0.2.1] - 2026-08-20
|
|
9
38
|
|
|
10
39
|
### Added
|
package/README.md
CHANGED
|
@@ -118,8 +118,8 @@ rvw walkthrough publish --stdin --json
|
|
|
118
118
|
構成されます。参照は意味のある複数行range、単行、またはfile全体を指せます。rvwは登録時にcommit、
|
|
119
119
|
path、指定された行範囲を検証します。publishしてもbrowserは開かれず、
|
|
120
120
|
active tabやscroll位置も変わりません。人間がviewerのWalkthroughを開き、必要なreferenceを選んだ
|
|
121
|
-
時だけ、exact sourceがdocument tab
|
|
122
|
-
|
|
121
|
+
時だけ、exact sourceがdocument tabへ開き、行指定があれば範囲全体が強調されます。通常clickは左ペイン、
|
|
122
|
+
`Cmd` / `Ctrl`を押しながら選べば右ペインへ開きます。説明tabは残るため、
|
|
123
123
|
複数のclaimと実装を任意の順序・任意のタイミングで往復できます。
|
|
124
124
|
|
|
125
125
|
Walkthrough本文のinline referenceとMermaid node linkは維持しますが、同じ参照を横や下へ列挙する
|
|
@@ -198,7 +198,13 @@ reference付きartifactとして検証してpublishします。Walkthrough全体
|
|
|
198
198
|
PRは常にcode/GitHub read-onlyで調査します。同梱driverがcursor resumeとRFC 7464 ingestを行い、
|
|
199
199
|
batchをclaimすると各threadへ`🔎 確認中です…`をAgent往復なしに即時返信し、
|
|
200
200
|
完了時は同じreplyを最終結果へ編集します。Skill同梱のtask-state toolがrepository外のSQLiteへcursor、
|
|
201
|
-
queue、retry、thread
|
|
201
|
+
queue、retry、batch内のthread単位status post、自己返信抑制をtransactionalに保存します。同じthreadへ
|
|
202
|
+
後から返信が追加された場合は新しいstatus postを返信するため、以前の回答は書き換えません。親taskは
|
|
203
|
+
起動前にsubagent枠を予約し、driverはその枠数までだけbatchをclaimします。acknowledge済みbatchは大小や
|
|
204
|
+
変更有無にかかわらず同じscheduling turnでfresh subagentへ委譲し、親taskは直接処理しません。lease解放後の
|
|
205
|
+
同一PR follow-upと期限到達したretryはdriverがtask stateから自動的に再開します。
|
|
206
|
+
調査結果、実装内容、test結果が具体的なcodeに基づく場合、Skillは最終replyからexact commitの有用な
|
|
207
|
+
line rangeへ`rvw-ref:` linkを付け、reviewerが根拠へ直接移動できるようにします。
|
|
202
208
|
|
|
203
209
|
三つのSkillはSQLiteを直接読まず、`rvw protocol --json`、`rvw comment ... --json`、
|
|
204
210
|
`rvw walkthrough get/update/publish/delete ... --json`、`rvw pr sync --stdin --json`だけを利用します。
|
|
@@ -253,9 +259,11 @@ rvw pr attach <PR_REF> --repository <PATH> --json
|
|
|
253
259
|
stdinをcloseし、shellではpipe、quoted heredoc、input redirectionのいずれかを使います。起動済みの
|
|
254
260
|
対話commandへJSONと改行だけを送るとEOF待ちになります。
|
|
255
261
|
|
|
256
|
-
`comment create`は登録済みPR、通常のcomment target、本文、任意のAgent
|
|
262
|
+
`comment create`は登録済みPR、通常のcomment target、本文、任意のAgent名、投稿単位code referenceをstdin JSONで受け取り、
|
|
257
263
|
未解決のroot threadを一件作成します。repository targetはexact commit、path、任意のinclusive line rangeを
|
|
258
264
|
指定し、viewerと同じ文書・行検証を通ります。作成してもbrowserを開かず、tabやcommit選択を変更しません。
|
|
265
|
+
同梱Skillは具体的なcode上のclaimにnavigation価値のある根拠がある場合、Walkthroughと同じくtyped
|
|
266
|
+
referenceを既定で使います。
|
|
259
267
|
|
|
260
268
|
`comment list`は未解決を既定として`unresolved` / `resolved` / `all`をページング列挙し、各threadの
|
|
261
269
|
root post preview、post件数、最新head時点のOutdated判定を返します。`hasMore`なら`nextOffset`から
|
|
@@ -295,6 +303,18 @@ maintainer向けのversion、tag、npm staged publishing、障害対応手順は
|
|
|
295
303
|
|
|
296
304
|
## 開発
|
|
297
305
|
|
|
306
|
+
source checkoutでは、GitHub接続や保存済みPRを用意せずにrepository規模のviewerを確認できます。
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
pnpm demo
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
このデモは小さなE2E fixtureとは分離され、現在のcheckoutにある直近6件のfirst-parent commitを
|
|
313
|
+
一つのsynthetic PRとして表示します。tree、文書、diff、検索はworktreeではなく実際のGit objectから読み、
|
|
314
|
+
100件以上の実在file、複数commit、変更外のtest・document・Skill、初期commentとWalkthroughを含みます。
|
|
315
|
+
browserを自動で開かない場合は`pnpm demo -- --no-open`、portを変える場合は`RVW_DEMO_PORT`を指定します。
|
|
316
|
+
デモを停止するには起動したterminalでCtrl+Cを押してください。
|
|
317
|
+
|
|
298
318
|
```bash
|
|
299
319
|
pnpm install --frozen-lockfile
|
|
300
320
|
pnpm check
|
package/dist/cli.mjs
CHANGED
|
@@ -19759,12 +19759,15 @@ import path5 from "node:path";
|
|
|
19759
19759
|
import { TextDecoder as TextDecoder2 } from "node:util";
|
|
19760
19760
|
|
|
19761
19761
|
// src/shared/constants.ts
|
|
19762
|
-
var APP_VERSION = "0.2.
|
|
19762
|
+
var APP_VERSION = "0.2.3";
|
|
19763
19763
|
var PROTOCOL_VERSION = 3;
|
|
19764
19764
|
var VIEWER_ID_HEADER = "x-rvw-viewer-id";
|
|
19765
19765
|
var GIT_OBJECT_ID_PATTERN = /^[0-9a-f]{40,64}$/i;
|
|
19766
19766
|
var MAX_TEXT_DOCUMENT_BYTES = 1024 * 1024;
|
|
19767
19767
|
var MAX_MARKDOWN_ASSET_BYTES = 5 * 1024 * 1024;
|
|
19768
|
+
var MAX_GITHUB_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
19769
|
+
var MAX_GITHUB_ATTACHMENT_STDERR_BYTES = 64 * 1024;
|
|
19770
|
+
var GITHUB_ATTACHMENT_TIMEOUT_MS = 3e4;
|
|
19768
19771
|
var MAX_COMMENT_BODY_BYTES = 64 * 1024;
|
|
19769
19772
|
var MAX_AUTHOR_LABEL_CHARACTERS = 100;
|
|
19770
19773
|
var MAX_IDEMPOTENCY_KEY_CHARACTERS = 200;
|
|
@@ -20414,6 +20417,101 @@ var GitClient = class {
|
|
|
20414
20417
|
}
|
|
20415
20418
|
};
|
|
20416
20419
|
|
|
20420
|
+
// src/shared/image-assets.ts
|
|
20421
|
+
var SUPPORTED_IMAGE_EXTENSIONS = [
|
|
20422
|
+
".png",
|
|
20423
|
+
".jpg",
|
|
20424
|
+
".jpeg",
|
|
20425
|
+
".gif",
|
|
20426
|
+
".webp",
|
|
20427
|
+
".avif",
|
|
20428
|
+
".svg"
|
|
20429
|
+
];
|
|
20430
|
+
var githubAttachmentPathPattern = /^\/user-attachments\/assets\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20431
|
+
var utf8Fatal2 = new TextDecoder("utf-8", { fatal: true });
|
|
20432
|
+
function hasBytes(bytes, offset, expected) {
|
|
20433
|
+
return expected.every((value, index2) => bytes[offset + index2] === value);
|
|
20434
|
+
}
|
|
20435
|
+
function ascii(bytes, offset, length) {
|
|
20436
|
+
return String.fromCharCode(...bytes.subarray(offset, offset + length));
|
|
20437
|
+
}
|
|
20438
|
+
function uint16Le(bytes, offset) {
|
|
20439
|
+
return (bytes[offset] ?? 0) | (bytes[offset + 1] ?? 0) << 8;
|
|
20440
|
+
}
|
|
20441
|
+
function uint32Be(bytes, offset) {
|
|
20442
|
+
return (bytes[offset] ?? 0) * 16777216 + ((bytes[offset + 1] ?? 0) << 16) + ((bytes[offset + 2] ?? 0) << 8) + (bytes[offset + 3] ?? 0) >>> 0;
|
|
20443
|
+
}
|
|
20444
|
+
function uint32Le(bytes, offset) {
|
|
20445
|
+
return (bytes[offset] ?? 0) + ((bytes[offset + 1] ?? 0) << 8) + ((bytes[offset + 2] ?? 0) << 16) + (bytes[offset + 3] ?? 0) * 16777216 >>> 0;
|
|
20446
|
+
}
|
|
20447
|
+
function isSvg(bytes) {
|
|
20448
|
+
if (bytes.length === 0) return false;
|
|
20449
|
+
let text3;
|
|
20450
|
+
try {
|
|
20451
|
+
text3 = utf8Fatal2.decode(bytes);
|
|
20452
|
+
} catch {
|
|
20453
|
+
return false;
|
|
20454
|
+
}
|
|
20455
|
+
const withoutBom = text3.charCodeAt(0) === 65279 ? text3.slice(1) : text3;
|
|
20456
|
+
let candidate = withoutBom.trimStart();
|
|
20457
|
+
const declaration = /^<\?xml(?:\s[^?]*)?\?>/iu.exec(candidate);
|
|
20458
|
+
if (declaration) candidate = candidate.slice(declaration[0].length).trimStart();
|
|
20459
|
+
while (candidate.startsWith("<!--")) {
|
|
20460
|
+
const commentEnd = candidate.indexOf("-->");
|
|
20461
|
+
if (commentEnd < 0) return false;
|
|
20462
|
+
candidate = candidate.slice(commentEnd + 3).trimStart();
|
|
20463
|
+
}
|
|
20464
|
+
return /^<svg(?:\s|>)/iu.test(candidate);
|
|
20465
|
+
}
|
|
20466
|
+
function isSupportedImagePath(filePath) {
|
|
20467
|
+
const normalized = filePath.toLowerCase();
|
|
20468
|
+
return SUPPORTED_IMAGE_EXTENSIONS.some((extension2) => normalized.endsWith(extension2));
|
|
20469
|
+
}
|
|
20470
|
+
function canonicalGitHubAttachmentUrl(value) {
|
|
20471
|
+
if (!value?.startsWith("https://github.com/")) return null;
|
|
20472
|
+
let parsed;
|
|
20473
|
+
try {
|
|
20474
|
+
parsed = new URL(value);
|
|
20475
|
+
} catch {
|
|
20476
|
+
return null;
|
|
20477
|
+
}
|
|
20478
|
+
if (parsed.protocol !== "https:" || parsed.hostname !== "github.com" || parsed.username !== "" || parsed.password !== "" || parsed.port !== "" || parsed.search !== "" || parsed.hash !== "" || !githubAttachmentPathPattern.test(parsed.pathname)) {
|
|
20479
|
+
return null;
|
|
20480
|
+
}
|
|
20481
|
+
return parsed.href;
|
|
20482
|
+
}
|
|
20483
|
+
function detectImageContentType(bytes) {
|
|
20484
|
+
if (bytes.length >= 33 && hasBytes(bytes, 0, [137, 80, 78, 71, 13, 10, 26, 10]) && ascii(bytes, 12, 4) === "IHDR" && uint32Be(bytes, 16) > 0 && uint32Be(bytes, 20) > 0) {
|
|
20485
|
+
return "image/png";
|
|
20486
|
+
}
|
|
20487
|
+
if (bytes.length >= 10 && hasBytes(bytes, 0, [255, 216, 255]) && hasBytes(bytes, bytes.length - 2, [255, 217])) {
|
|
20488
|
+
return "image/jpeg";
|
|
20489
|
+
}
|
|
20490
|
+
const gifHeader = bytes.length >= 13 ? ascii(bytes, 0, 6) : "";
|
|
20491
|
+
if ((gifHeader === "GIF87a" || gifHeader === "GIF89a") && uint16Le(bytes, 6) > 0 && uint16Le(bytes, 8) > 0) {
|
|
20492
|
+
return "image/gif";
|
|
20493
|
+
}
|
|
20494
|
+
if (bytes.length >= 20 && ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WEBP" && ["VP8 ", "VP8L", "VP8X"].includes(ascii(bytes, 12, 4)) && uint32Le(bytes, 4) >= 12 && uint32Le(bytes, 4) + 8 <= bytes.length) {
|
|
20495
|
+
return "image/webp";
|
|
20496
|
+
}
|
|
20497
|
+
if (bytes.length >= 16 && ascii(bytes, 4, 4) === "ftyp") {
|
|
20498
|
+
const boxSize = uint32Be(bytes, 0);
|
|
20499
|
+
if (boxSize >= 16 && boxSize <= bytes.length) {
|
|
20500
|
+
const brands = [ascii(bytes, 8, 4)];
|
|
20501
|
+
for (let offset = 16; offset + 4 <= boxSize; offset += 4) {
|
|
20502
|
+
brands.push(ascii(bytes, offset, 4));
|
|
20503
|
+
}
|
|
20504
|
+
if (brands.some((brand) => brand === "avif" || brand === "avis")) {
|
|
20505
|
+
return "image/avif";
|
|
20506
|
+
}
|
|
20507
|
+
}
|
|
20508
|
+
}
|
|
20509
|
+
return isSvg(bytes) ? "image/svg+xml" : null;
|
|
20510
|
+
}
|
|
20511
|
+
function imageContentTypeHeader(contentType2) {
|
|
20512
|
+
return contentType2 === "image/svg+xml" ? `${contentType2}; charset=utf-8` : contentType2;
|
|
20513
|
+
}
|
|
20514
|
+
|
|
20417
20515
|
// src/infrastructure/github/github-client.ts
|
|
20418
20516
|
var ghPullRequestSchema = external_exports.object({
|
|
20419
20517
|
author: external_exports.object({ login: external_exports.string().min(1) }).nullable(),
|
|
@@ -20439,6 +20537,10 @@ function parsePullRequestUrl(url2) {
|
|
|
20439
20537
|
return { owner: match2[1], repository: match2[2], number: Number(match2[3]) };
|
|
20440
20538
|
}
|
|
20441
20539
|
var GitHubClient = class {
|
|
20540
|
+
constructor(processRunner = runProcess) {
|
|
20541
|
+
this.processRunner = processRunner;
|
|
20542
|
+
}
|
|
20543
|
+
processRunner;
|
|
20442
20544
|
async doctor() {
|
|
20443
20545
|
const version2 = await runText("gh", ["--version"]);
|
|
20444
20546
|
const auth = await runProcess("gh", ["auth", "status", "--hostname", "github.com"], {
|
|
@@ -20523,6 +20625,31 @@ var GitHubClient = class {
|
|
|
20523
20625
|
isDraft: parsed.data.isDraft
|
|
20524
20626
|
};
|
|
20525
20627
|
}
|
|
20628
|
+
async getAttachment(absoluteUrl) {
|
|
20629
|
+
const canonicalUrl = canonicalGitHubAttachmentUrl(absoluteUrl);
|
|
20630
|
+
if (!canonicalUrl) {
|
|
20631
|
+
throw new RvwError("INVALID_INPUT", "GitHub user attachment URL\u304C\u4E0D\u6B63\u3067\u3059\u3002");
|
|
20632
|
+
}
|
|
20633
|
+
try {
|
|
20634
|
+
const result = await this.processRunner("gh", ["api", canonicalUrl], {
|
|
20635
|
+
timeoutMs: GITHUB_ATTACHMENT_TIMEOUT_MS,
|
|
20636
|
+
maxStdoutBytes: MAX_GITHUB_ATTACHMENT_BYTES,
|
|
20637
|
+
maxStderrBytes: MAX_GITHUB_ATTACHMENT_STDERR_BYTES
|
|
20638
|
+
});
|
|
20639
|
+
return { content: result.stdout, byteLength: result.stdout.byteLength };
|
|
20640
|
+
} catch (error51) {
|
|
20641
|
+
if (error51 instanceof RvwError && error51.code === "PROCESS_OUTPUT_LIMIT" && error51.details?.stream === "stdout") {
|
|
20642
|
+
throw new RvwError("FILE_TOO_LARGE", "GitHub attachment\u306F10 MiB\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002", {
|
|
20643
|
+
status: 413
|
|
20644
|
+
});
|
|
20645
|
+
}
|
|
20646
|
+
throw new RvwError(
|
|
20647
|
+
"GITHUB_ERROR",
|
|
20648
|
+
"GitHub attachment\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002gh\u8A8D\u8A3C\u3068\u753B\u50CF\u306E\u95B2\u89A7\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
20649
|
+
{ status: 502 }
|
|
20650
|
+
);
|
|
20651
|
+
}
|
|
20652
|
+
}
|
|
20526
20653
|
};
|
|
20527
20654
|
|
|
20528
20655
|
// src/application/rvw-service.ts
|
|
@@ -28398,6 +28525,21 @@ var RvwService = class {
|
|
|
28398
28525
|
await this.assertCommitAvailable(pullRequest, sourceOid);
|
|
28399
28526
|
return await this.git.readRepositoryAsset(pullRequest.localRepositoryPath, sourceOid, filePath);
|
|
28400
28527
|
}
|
|
28528
|
+
async getGitHubAttachment(pullRequestId, absoluteUrl) {
|
|
28529
|
+
this.getPullRequest(pullRequestId);
|
|
28530
|
+
const canonicalUrl = canonicalGitHubAttachmentUrl(absoluteUrl);
|
|
28531
|
+
if (!canonicalUrl) {
|
|
28532
|
+
throw new RvwError("INVALID_INPUT", "GitHub user attachment URL\u304C\u4E0D\u6B63\u3067\u3059\u3002");
|
|
28533
|
+
}
|
|
28534
|
+
const attachment = await this.github.getAttachment(canonicalUrl);
|
|
28535
|
+
const contentType2 = detectImageContentType(attachment.content);
|
|
28536
|
+
if (!contentType2) {
|
|
28537
|
+
throw new RvwError("UNSUPPORTED_IMAGE", "GitHub attachment\u306F\u5BFE\u5FDC\u753B\u50CF\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002", {
|
|
28538
|
+
status: 415
|
|
28539
|
+
});
|
|
28540
|
+
}
|
|
28541
|
+
return { ...attachment, contentType: contentType2 };
|
|
28542
|
+
}
|
|
28401
28543
|
async getDiff(ref) {
|
|
28402
28544
|
const [old, next] = await Promise.all([
|
|
28403
28545
|
ref.old ? this.getDocument(ref.old) : Promise.resolve(null),
|
|
@@ -33257,17 +33399,25 @@ function parseBooleanQuery(value, name) {
|
|
|
33257
33399
|
if (value === "true") return true;
|
|
33258
33400
|
throw new RvwError("INVALID_INPUT", `${name} query\u306Ftrue\u307E\u305F\u306Ffalse\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
|
|
33259
33401
|
}
|
|
33260
|
-
function
|
|
33261
|
-
|
|
33262
|
-
|
|
33263
|
-
|
|
33264
|
-
"
|
|
33265
|
-
|
|
33266
|
-
|
|
33267
|
-
|
|
33268
|
-
|
|
33269
|
-
|
|
33270
|
-
|
|
33402
|
+
function repositoryAssetContentType(filePath, content3) {
|
|
33403
|
+
if (!isSupportedImagePath(filePath)) return null;
|
|
33404
|
+
const contentType2 = detectImageContentType(content3);
|
|
33405
|
+
if (!contentType2) {
|
|
33406
|
+
throw new RvwError("UNSUPPORTED_IMAGE", "repository asset\u306F\u5BFE\u5FDC\u753B\u50CF\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002", {
|
|
33407
|
+
status: 415
|
|
33408
|
+
});
|
|
33409
|
+
}
|
|
33410
|
+
return contentType2;
|
|
33411
|
+
}
|
|
33412
|
+
function setImageResponseHeaders(setHeader, contentType2) {
|
|
33413
|
+
setHeader("content-type", imageContentTypeHeader(contentType2));
|
|
33414
|
+
setHeader("cache-control", "private, max-age=31536000, immutable");
|
|
33415
|
+
setHeader("x-content-type-options", "nosniff");
|
|
33416
|
+
setHeader("content-disposition", "inline");
|
|
33417
|
+
setHeader("cross-origin-resource-policy", "same-origin");
|
|
33418
|
+
if (contentType2 === "image/svg+xml") {
|
|
33419
|
+
setHeader("content-security-policy", svgAssetContentSecurityPolicy);
|
|
33420
|
+
}
|
|
33271
33421
|
}
|
|
33272
33422
|
function documentRefFromQuery(pullRequestId, query) {
|
|
33273
33423
|
if (query.kind === "pull-request-markdown") {
|
|
@@ -33395,17 +33545,36 @@ function createApp(service, options) {
|
|
|
33395
33545
|
const ref = documentRefFromQuery(context.req.param("id"), context.req.query());
|
|
33396
33546
|
return context.json({ ok: true, document: await service.getDocument(ref) });
|
|
33397
33547
|
});
|
|
33398
|
-
app.
|
|
33548
|
+
app.on(["GET", "HEAD"], "/api/pull-requests/:id/markdown-asset", async (context) => {
|
|
33399
33549
|
const sourceOid = oidQuery(context.req.query("sourceOid"), "sourceOid");
|
|
33400
33550
|
const filePath = requiredQuery(context.req.query("path"), "path");
|
|
33401
33551
|
const asset = await service.getRepositoryAsset(context.req.param("id"), sourceOid, filePath);
|
|
33402
|
-
|
|
33403
|
-
|
|
33404
|
-
|
|
33405
|
-
|
|
33406
|
-
context.header("content-
|
|
33552
|
+
const contentType2 = repositoryAssetContentType(filePath, asset.content);
|
|
33553
|
+
if (contentType2) {
|
|
33554
|
+
setImageResponseHeaders((name, value) => context.header(name, value), contentType2);
|
|
33555
|
+
} else {
|
|
33556
|
+
context.header("content-type", "application/octet-stream");
|
|
33557
|
+
context.header("cache-control", "private, max-age=31536000, immutable");
|
|
33558
|
+
context.header("x-content-type-options", "nosniff");
|
|
33559
|
+
}
|
|
33560
|
+
return context.req.method === "HEAD" ? context.body(null) : context.body(Uint8Array.from(asset.content));
|
|
33561
|
+
});
|
|
33562
|
+
app.get("/api/pull-requests/:id/github-attachment", async (context) => {
|
|
33563
|
+
const fetchSite = context.req.header("sec-fetch-site");
|
|
33564
|
+
const origin = context.req.header("origin");
|
|
33565
|
+
if (fetchSite !== void 0 && fetchSite !== "same-origin" && fetchSite !== "none" || origin !== void 0 && origin !== options.security.expectedOrigin) {
|
|
33566
|
+
throw new RvwError(
|
|
33567
|
+
"INVALID_ORIGIN",
|
|
33568
|
+
"cross-origin attachment request\u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
33569
|
+
{
|
|
33570
|
+
status: 403
|
|
33571
|
+
}
|
|
33572
|
+
);
|
|
33407
33573
|
}
|
|
33408
|
-
|
|
33574
|
+
const absoluteUrl = requiredQuery(context.req.query("url"), "url");
|
|
33575
|
+
const attachment = await service.getGitHubAttachment(context.req.param("id"), absoluteUrl);
|
|
33576
|
+
setImageResponseHeaders((name, value) => context.header(name, value), attachment.contentType);
|
|
33577
|
+
return context.body(Uint8Array.from(attachment.content));
|
|
33409
33578
|
});
|
|
33410
33579
|
app.get("/api/pull-requests/:id/diff", async (context) => {
|
|
33411
33580
|
const pullRequestId = context.req.param("id");
|