github-mobile-reader 0.1.0 β†’ 0.1.2

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/README.ko.md ADDED
@@ -0,0 +1,585 @@
1
+ # πŸ“– github-mobile-reader
2
+
3
+ > `github-mobile-reader`λŠ” git diffλ₯Ό κΉ”λ”ν•˜κ²Œ μ„Έλ‘œ 슀크둀둜 읽을 수 μžˆλŠ” Markdown λ¬Έμ„œλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€ β€” 더 이상 쒌우 ν•€μΉ˜μ€Œμ΄λ‚˜ κ°€λ‘œ μŠ€μ™€μ΄ν”„λŠ” ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/github-mobile-reader.svg)](https://www.npmjs.com/package/github-mobile-reader)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js β‰₯ 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
8
+
9
+ > μ˜μ–΄ λ¬Έμ„œλŠ” [README.md](./README.md)μ—μ„œ ν™•μΈν•˜μ„Έμš”.
10
+
11
+ ---
12
+
13
+ ## 문제 상황
14
+
15
+ GitHub의 λͺ¨λ°”일 μ›Ή λ·°λŠ” μ½”λ“œλ₯Ό κ³ μ • λ„ˆλΉ„μ˜ λͺ¨λ…ΈμŠ€νŽ˜μ΄μŠ€ λΈ”λ‘μœΌλ‘œ λ Œλ”λ§ν•©λ‹ˆλ‹€. κΈ΄ 쀄은 κ°€λ‘œ μŠ€ν¬λ‘€μ„ μš”κ΅¬ν•˜κ³ , 깊게 μ€‘μ²©λœ λ‘œμ§μ€ ν•œλˆˆμ— νŒŒμ•…μ΄ λΆˆκ°€λŠ₯ν•˜λ©°, μΆœν‡΄κ·Ό μ§€ν•˜μ² μ—μ„œ PR 리뷰λ₯Ό ν•˜λŠ” 건 사싀상 λΆˆκ°€λŠ₯에 κ°€κΉμŠ΅λ‹ˆλ‹€.
16
+
17
+ ## ν•΄κ²°μ±…
18
+
19
+ `github-mobile-reader`λŠ” git diffλ₯Ό νŒŒμ‹±ν•΄μ„œ **Logical Flow** β€” λ‹¨μˆœνžˆ μ–΄λ–€ λ¬Έμžκ°€ λ°”λ€Œμ—ˆλŠ”μ§€κ°€ μ•„λ‹ˆλΌ *μ½”λ“œκ°€ 무엇을 ν•˜λŠ”μ§€*λ₯Ό λ³΄μ—¬μ£ΌλŠ” κ°„κ²°ν•œ 트리 β€” λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 결과물은 μ–΄λ–€ ν™”λ©΄ λ„ˆλΉ„μ—μ„œλ„ μœ„μ—μ„œ μ•„λž˜λ‘œ μ½νžˆλŠ” Markdown λ¬Έμ„œμž…λ‹ˆλ‹€.
20
+
21
+ **Before** (κΈ°μ‘΄ diff, λͺ¨λ°”일 μ›Ή):
22
+
23
+ ```
24
+ ← μŠ€μ™€μ΄ν”„ β†’ μŠ€μ™€μ΄ν”„ β†’ μŠ€μ™€μ΄ν”„ β†’
25
+ + const result = data.map(item => item.value).filter(v => v > 10).reduce((a,b) => a+b, 0)
26
+ ```
27
+
28
+ **After** (Reader Markdown):
29
+
30
+ ```
31
+ data
32
+ └─ map(item β†’ value)
33
+ └─ filter(callback)
34
+ └─ reduce(callback)
35
+ ```
36
+
37
+ ---
38
+
39
+ ## μ£Όμš” κΈ°λŠ₯
40
+
41
+ - **μ˜μ‘΄μ„± 제둜 μ½”μ–΄** β€” νŒŒμ„œλŠ” Node.js β‰₯ 18이 μžˆλŠ” μ–΄λ””μ„œλ‚˜ λ™μž‘ν•©λ‹ˆλ‹€
42
+ - **이쀑 좜λ ₯ 포맷** β€” CJS (`require`)와 ESM (`import`) λͺ¨λ‘ 지원, TypeScript νƒ€μž… 포함
43
+ - **CLI** β€” `npx github-mobile-reader --repo owner/repo --pr 42` 둜 μ–΄λ–€ PR이든 μ¦‰μ‹œ λ³€ν™˜
44
+ - **GitHub Action** β€” λ ˆν¬μ— YAML 파일 ν•˜λ‚˜λ§Œ μΆ”κ°€ν•˜λ©΄ PRλ§ˆλ‹€ Reader λ¬Έμ„œκ°€ μžλ™ μƒμ„±λ©λ‹ˆλ‹€
45
+ - **νŒŒμΌλ³„ 뢄리 좜λ ₯** β€” λ³€κ²½λœ JS/TS νŒŒμΌλ§ˆλ‹€ 독립적인 μ„Ήμ…˜μœΌλ‘œ 좜λ ₯
46
+ - **JSX/Tailwind 인식** β€” `.jsx`/`.tsx` νŒŒμΌμ€ μ»΄ν¬λ„ŒνŠΈ 트리(`🎨 JSX Structure`)와 Tailwind 클래슀 diff(`πŸ’… Style Changes`)λ₯Ό 별도 μ„Ήμ…˜μœΌλ‘œ 뢄리 좜λ ₯
47
+ - **μ–‘λ°©ν–₯ diff 좔적** β€” μΆ”κ°€λœ μ½”λ“œμ™€ μ‚­μ œλœ μ½”λ“œλ₯Ό 각각 별도 μ„Ήμ…˜μœΌλ‘œ ν‘œμ‹œ
48
+ - **보수적 섀계** β€” νŒ¨ν„΄μ΄ μ• λ§€ν•  λ•ŒλŠ” 잘λͺ»λœ 정보λ₯Ό λ³΄μ—¬μ£ΌλŠ” λŒ€μ‹  덜 λ³΄μ—¬μ€λ‹ˆλ‹€
49
+ - **λ³΄μ•ˆ κΈ°λ³Έκ°’** β€” 토큰은 `$GITHUB_TOKEN` ν™˜κ²½λ³€μˆ˜λ‘œλ§Œ 읽음 β€” μ…Έ νžˆμŠ€ν† λ¦¬λ‚˜ `ps` λͺ©λ‘μ— λ…ΈμΆœλ˜λŠ” `--token` ν”Œλž˜κ·Έ μ—†μŒ
50
+
51
+ ---
52
+
53
+ ## λͺ©μ°¨
54
+
55
+ 1. [λΉ λ₯Έ μ‹œμž‘](#λΉ λ₯Έ-μ‹œμž‘)
56
+ 2. [μ–Έμ–΄ 지원](#μ–Έμ–΄-지원)
57
+ 3. [CLI μ‚¬μš©λ²•](#cli-μ‚¬μš©λ²•)
58
+ 4. [GitHub Action (ꢌμž₯)](#github-action-ꢌμž₯)
59
+ 5. [npm 라이브러리 μ‚¬μš©λ²•](#npm-라이브러리-μ‚¬μš©λ²•)
60
+ 6. [좜λ ₯ ν˜•μ‹](#좜λ ₯-ν˜•μ‹)
61
+ 7. [API 레퍼런슀](#api-레퍼런슀)
62
+ 8. [νŒŒμ„œ λ™μž‘ 원리](#νŒŒμ„œ-λ™μž‘-원리)
63
+ 9. [κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°)
64
+ 10. [λΌμ΄μ„ μŠ€](#λΌμ΄μ„ μŠ€)
65
+
66
+ ---
67
+
68
+ ## CLI μ‚¬μš©λ²•
69
+
70
+ ν„°λ―Έλ„μ—μ„œ `github-mobile-reader`λ₯Ό λ°”λ‘œ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€ β€” 별도 μ„€μ • 파일 λΆˆν•„μš”. GitHubμ—μ„œ PR diffλ₯Ό λ°›μ•„ λͺ¨λ°”일 μΉœν™”μ μΈ Markdown으둜 λ³€ν™˜ν•˜κ³  `./reader-output/`에 PRλ³„λ‘œ νŒŒμΌμ„ μ €μž₯ν•©λ‹ˆλ‹€.
71
+
72
+ ### 인증 (토큰 μ„€μ •)
73
+
74
+ CLI μ‹€ν–‰ **전에** ν™˜κ²½λ³€μˆ˜λ‘œ GitHub 토큰을 μ„€μ •ν•˜μ„Έμš”:
75
+
76
+ ```bash
77
+ export GITHUB_TOKEN=ghp_xxxx
78
+ npx github-mobile-reader --repo owner/repo --pr 42
79
+ ```
80
+
81
+ > **λ³΄μ•ˆ μ•ˆλ‚΄:** CLIλŠ” `--token` ν”Œλž˜κ·Έλ₯Ό μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ»€λ§¨λ“œλΌμΈ 인자둜 μ‹œν¬λ¦Ώμ„ μ „λ‹¬ν•˜λ©΄ μ…Έ νžˆμŠ€ν† λ¦¬μ™€ `ps` 좜λ ₯에 토큰이 λ…ΈμΆœλ©λ‹ˆλ‹€. λ°˜λ“œμ‹œ ν™˜κ²½λ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.
82
+
83
+ ### 단일 PR
84
+
85
+ ```bash
86
+ npx github-mobile-reader --repo owner/repo --pr 42
87
+ ```
88
+
89
+ ### 졜근 PR 전체
90
+
91
+ ```bash
92
+ npx github-mobile-reader --repo owner/repo --all
93
+ ```
94
+
95
+ ### μ˜΅μ…˜
96
+
97
+ | ν”Œλž˜κ·Έ | κΈ°λ³Έκ°’ | μ„€λͺ… |
98
+ | ----------- | ------------------ | ------------------------------------------------------- |
99
+ | `--repo` | *(ν•„μˆ˜)* | `owner/repo` ν˜•μ‹μ˜ λ ˆν¬μ§€ν† λ¦¬ |
100
+ | `--pr` | β€” | νŠΉμ • PR 번호 ν•˜λ‚˜ 처리 |
101
+ | `--all` | β€” | 졜근 PR 전체 처리 (`--limit`와 ν•¨κ»˜ μ‚¬μš©) |
102
+ | `--out` | `./reader-output` | μƒμ„±λœ `.md` 파일 μ €μž₯ 경둜 β€” μƒλŒ€ 경둜만 ν—ˆμš©, `..` λΆˆκ°€ |
103
+ | `--limit` | `10` | `--all` μ‚¬μš© μ‹œ κ°€μ Έμ˜¬ PR μ΅œλŒ€ 개수 |
104
+
105
+ 토큰: `$GITHUB_TOKEN` ν™˜κ²½λ³€μˆ˜μ—μ„œ 읽음 (미인증 μ‹œ 60 req/hr, 인증 μ‹œ 5 000 req/hr).
106
+
107
+ ### 좜λ ₯ κ²°κ³Ό
108
+
109
+ PRλ§ˆλ‹€ `reader-output/pr-<번호>.md` 파일 ν•˜λ‚˜κ°€ μƒμ„±λ©λ‹ˆλ‹€.
110
+
111
+ JSX/TSX νŒŒμΌμ€ μΆ”κ°€ μ„Ήμ…˜μ΄ μƒμ„±λ©λ‹ˆλ‹€:
112
+
113
+ ```
114
+ # πŸ“– PR #42 β€” My Feature
115
+
116
+ ## πŸ“„ `src/App.tsx`
117
+
118
+ ### 🧠 Logical Flow ← JS 둜직 트리
119
+ ### 🎨 JSX Structure ← μ»΄ν¬λ„ŒνŠΈ 계측 ꡬ쑰 (JSX/TSX μ „μš©)
120
+ ### πŸ’… Style Changes ← μΆ”κ°€/제거된 Tailwind 클래슀 (JSX/TSX μ „μš©)
121
+ ### βœ… Added Code
122
+ ### ❌ Removed Code
123
+ ```
124
+
125
+ > **μ°Έκ³ :** `reader-output/`λŠ” 기본적으둜 `.gitignore`에 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€ β€” μƒμ„±λœ νŒŒμΌμ€ λ‘œμ»¬μ—λ§Œ μ €μž₯되며 λ ˆν¬μ§€ν† λ¦¬μ— μ»€λ°‹λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
126
+
127
+ ---
128
+
129
+ ## λΉ λ₯Έ μ‹œμž‘
130
+
131
+ ```bash
132
+ npm install github-mobile-reader
133
+ ```
134
+
135
+ ```ts
136
+ import { generateReaderMarkdown } from "github-mobile-reader";
137
+ import { execSync } from "child_process";
138
+
139
+ const diff = execSync("git diff HEAD~1", { encoding: "utf8" });
140
+ const markdown = generateReaderMarkdown(diff, { file: "src/utils.ts" });
141
+
142
+ console.log(markdown);
143
+ ```
144
+
145
+ ---
146
+
147
+ ## μ–Έμ–΄ 지원
148
+
149
+ νŒŒμ„œλŠ” μ •κ·œμ‹ 기반 νŒ¨ν„΄ 맀칭으둜 λ™μž‘ν•˜λ―€λ‘œ κΈ°μˆ μ μœΌλ‘œλŠ” μ–΄λ–€ μ–Έμ–΄μ˜ diff도 μž…λ ₯받을 수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ 감지 νŒ¨ν„΄μ΄ JavaScript/TypeScript 문법에 맞좰 μ„€κ³„λ˜μ–΄ μžˆμ–΄ **Logical Flow 좜λ ₯ ν’ˆμ§ˆμ΄ μ–Έμ–΄λ§ˆλ‹€ λ‹€λ¦…λ‹ˆλ‹€**.
150
+
151
+ ### ν˜„μž¬ 지원 ν˜„ν™© (v0.1)
152
+
153
+ | μ–Έμ–΄ | ν™•μž₯자 | ν’ˆμ§ˆ | λΉ„κ³  |
154
+ | ----------------------- | ------------------------- | :------------: | --------------------------------------------------------------------------- |
155
+ | **JavaScript** | `.js` `.mjs` `.cjs` | βœ… μ™„μ „ | νŒŒμ„œμ˜ κΈ°μ€€ μ–Έμ–΄ |
156
+ | **TypeScript** | `.ts` | βœ… μ™„μ „ | JS μƒμœ„ μ§‘ν•© β€” λͺ¨λ“  νŒ¨ν„΄ 적용 |
157
+ | **React JSX** | `.jsx` | βœ… μ™„μ „ | JS와 λ™μΌν•œ 문법 |
158
+ | **React TSX** | `.tsx` | βœ… μ™„μ „ | TS와 λ™μΌν•œ 문법 |
159
+ | **Next.js** | `.js` `.ts` `.jsx` `.tsx` | βœ… μ™„μ „ | JS/TS μœ„μ—μ„œ λ™μž‘ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬ |
160
+ | **Java** | `.java` | ⚠️ λΆ€λΆ„ (~55%) | `if/for/while`κ³Ό 체이닝은 λ™μž‘; ν•¨μˆ˜ μ„ μ–Έ 감지 μ‹€νŒ¨ (`const/let/var` μ—†μŒ) |
161
+ | **C#** | `.cs` | ⚠️ λΆ€λΆ„ (~35%) | LINQ 체이닝(`.Where().Select()`)은 λ™μž‘; `using`/`namespace`/`class` 미감지 |
162
+ | **C** | `.c` `.h` | ❌ μ΅œμ†Œ (~15%) | λ§€μΉ­ ν‚€μ›Œλ“œ μ—†μŒ; 포인터 문법(`->`, `*`) 미지원 |
163
+ | **Python, Go, Rust λ“±** | β€” | πŸ”œ μ˜ˆμ • | μ•„λž˜ λ‘œλ“œλ§΅ μ°Έκ³  |
164
+
165
+ > **μ°Έκ³ :** Java, C#, C νŒŒμΌμ€ 기본적으둜 GitHub Actionμ—μ„œ μ²˜λ¦¬λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
166
+ > Action은 `.js .jsx .ts .tsx .mjs .cjs` 파일만 μŠ€μΊ”ν•©λ‹ˆλ‹€ ([`src/action.ts` 66번째 쀄](src/action.ts)).
167
+ > λ‹€λ₯Έ μ–Έμ–΄λ₯Ό μ²˜λ¦¬ν•˜λ €λ©΄ μ»€μŠ€ν…€ μ–΄λŒ‘ν„°κ°€ ν•„μš”ν•©λ‹ˆλ‹€ ([κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°) μ°Έκ³ ).
168
+
169
+ ### JS/TS/React/Next.jsκ°€ μ™„μ „ μ§€μ›λ˜λŠ” 이유
170
+
171
+ λ„€ κ°€μ§€ λͺ¨λ‘ λ™μΌν•œ 기반 문법을 κ³΅μœ ν•©λ‹ˆλ‹€. νŒŒμ„œκ°€ μΈμ‹ν•˜λŠ” 것:
172
+
173
+ - **λ©”μ„œλ“œ 체이닝** β€” `)`λ‚˜ `}`둜 λλ‚˜λŠ” 쀄 λ‹€μŒμ— `.`으둜 μ‹œμž‘ν•˜λŠ” 쀄
174
+ ```ts
175
+ data
176
+ .filter((item) => item.active) // P1 μ²΄μ΄λ‹μœΌλ‘œ 감지
177
+ .map((item) => item.value); // P1 μ²΄μ΄λ‹μœΌλ‘œ 감지
178
+ ```
179
+ - **ν•¨μˆ˜ μ„ μ–Έ** β€” `const`, `let`, `var`, `function`, `async`
180
+ - **쑰건문** β€” `if / else / switch`
181
+ - **반볡문** β€” `for / while`
182
+ - **λ…Έμ΄μ¦ˆ 필터링** β€” `import`, `export`, `type`, `interface`, `console.log`λŠ” μžλ™μœΌλ‘œ 제거
183
+
184
+ ### C / C# / Javaκ°€ μ œν•œμ μΈ 이유
185
+
186
+ 이 언어듀은 μœ„ νŒ¨ν„΄μ— λŒ€ν•΄ λ‹€λ₯Έ ν‘œκΈ° 방식을 μ‚¬μš©ν•©λ‹ˆλ‹€:
187
+
188
+ | κ°œλ… | JS/TS (βœ… 감지됨) | Java / C# / C (❌ 미감지) |
189
+ | ------------- | ---------------------- | -------------------------------- |
190
+ | λ³€μˆ˜ μ„ μ–Έ | `const x = …` | `int x = …` / `String x = …` |
191
+ | ν™”μ‚΄ν‘œ 콜백 | `x => x.value` | μ–Έμ–΄λ§ˆλ‹€ λžŒλ‹€ 문법 닀름 |
192
+ | λ…Έμ΄μ¦ˆ import | `import` / `export` | `using` / `#include` / `package` |
193
+ | 비동기 ν•¨μˆ˜ | `async function foo()` | `async Task<T> Foo()` |
194
+
195
+ ### λ‘œλ“œλ§΅ β€” Language Adapter μ‹œμŠ€ν…œ (v0.2)
196
+
197
+ μΆ”κ°€ μ–Έμ–΄ 지원을 μœ„ν•΄ **Language Adapter** μ•„ν‚€ν…μ²˜κ°€ κ³„νšλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:
198
+
199
+ ```
200
+ src/languages/
201
+ β”œβ”€β”€ base.adapter.ts ← 곡톡 μΈν„°νŽ˜μ΄μŠ€
202
+ β”œβ”€β”€ js-ts.adapter.ts ← ν˜„μž¬ 둜직 (parser.tsμ—μ„œ 뢄리)
203
+ β”œβ”€β”€ java.adapter.ts ← public/private/void μ„ μ–Έ, Stream 체이닝
204
+ └── csharp.adapter.ts ← using/namespace, LINQ 체이닝
205
+ ```
206
+
207
+ 각 μ–΄λŒ‘ν„°κ°€ μ œκ³΅ν•˜λŠ” 것:
208
+
209
+ - 지원 파일 ν™•μž₯자 λͺ©λ‘
210
+ - ν•¨μˆ˜ μ„ μ–Έ 감지 νŒ¨ν„΄
211
+ - λ¬΄μ‹œν•  ν‚€μ›Œλ“œ λͺ©λ‘ (λ…Έμ΄μ¦ˆ)
212
+ - 체이닝 ν‘œκΈ° 방식 (점(`.`) vs. ν™”μ‚΄ν‘œ(`->`))
213
+
214
+ μ–Έμ–΄ μ–΄λŒ‘ν„°λ₯Ό κΈ°μ—¬ν•˜κ³  μ‹Άλ‹€λ©΄ [κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°)λ₯Ό ν™•μΈν•˜μ„Έμš”.
215
+
216
+ ---
217
+
218
+ ## GitHub Action (ꢌμž₯)
219
+
220
+ 이 라이브러리λ₯Ό μ‚¬μš©ν•˜λŠ” κ°€μž₯ μ‰¬μš΄ λ°©λ²•μž…λ‹ˆλ‹€. λ§€ PRλ§ˆλ‹€ μžλ™μœΌλ‘œ:
221
+
222
+ 1. λ³€κ²½λœ `.js` / `.ts` 파일의 diffλ₯Ό νŒŒμ‹±
223
+ 2. `docs/reader/pr-<번호>.md` νŒŒμΌμ„ λ ˆν¬μ— μ €μž₯
224
+ 3. PR에 μš”μ•½ μ½”λ©˜νŠΈλ₯Ό μžλ™μœΌλ‘œ λ‹¬μ•„μ€λ‹ˆλ‹€
225
+
226
+ ### Step 1 β€” μ›Œν¬ν”Œλ‘œμš° 파일 μΆ”κ°€
227
+
228
+ λ ˆν¬μ— `.github/workflows/mobile-reader.yml`을 λ§Œλ“€μ–΄ μ£Όμ„Έμš”:
229
+
230
+ ```yaml
231
+ name: πŸ“– Mobile Reader
232
+
233
+ on:
234
+ pull_request:
235
+ types: [opened, synchronize, reopened]
236
+
237
+ permissions:
238
+ contents: write # .md 파일 컀밋
239
+ pull-requests: write # PR μ½”λ©˜νŠΈ μž‘μ„±
240
+
241
+ jobs:
242
+ generate-reader:
243
+ name: Generate Mobile Reader View
244
+ runs-on: ubuntu-latest
245
+
246
+ steps:
247
+ - name: Checkout
248
+ uses: actions/checkout@v4
249
+ with:
250
+ fetch-depth: 0 # git diff에 전체 νžˆμŠ€ν† λ¦¬ ν•„μš”
251
+
252
+ - name: Generate Reader Markdown
253
+ uses: 3rdflr/github-mobile-reader@v1
254
+ with:
255
+ github_token: ${{ secrets.GITHUB_TOKEN }}
256
+ base_branch: ${{ github.base_ref }}
257
+ output_dir: docs/reader
258
+ env:
259
+ PR_NUMBER: ${{ github.event.pull_request.number }}
260
+
261
+ - name: Commit Reader Markdown
262
+ run: |
263
+ git config user.name "github-actions[bot]"
264
+ git config user.email "github-actions[bot]@users.noreply.github.com"
265
+ git add docs/reader/
266
+ if git diff --cached --quiet; then
267
+ echo "변경사항 μ—†μŒ"
268
+ else
269
+ git commit -m "docs(reader): PR #${{ github.event.pull_request.number }} λͺ¨λ°”일 리더 μ—…λ°μ΄νŠΈ [skip ci]"
270
+ git push
271
+ fi
272
+ ```
273
+
274
+ ### Step 2 β€” PR μ—΄κΈ°
275
+
276
+ 이게 μ „λΆ€μž…λ‹ˆλ‹€. 이후 λͺ¨λ“  PR에 μžλ™μœΌλ‘œ:
277
+
278
+ - `docs/reader/pr-<번호>.md` 파일 생성
279
+ - μƒμ„±λœ 파일 링크가 λ‹΄κΈ΄ PR μ½”λ©˜νŠΈ μžλ™ κ²Œμ‹œ
280
+
281
+ ### Action μž…λ ₯κ°’
282
+
283
+ | μž…λ ₯κ°’ | ν•„μˆ˜ | κΈ°λ³Έκ°’ | μ„€λͺ… |
284
+ | -------------- | ---- | ------------- | ---------------------------------- |
285
+ | `github_token` | βœ… | β€” | `${{ secrets.GITHUB_TOKEN }}` μ‚¬μš© |
286
+ | `base_branch` | ❌ | `main` | PR이 λ¨Έμ§€λ˜λŠ” λŒ€μƒ 브랜치 |
287
+ | `output_dir` | ❌ | `docs/reader` | μƒμ„±λœ `.md` 파일 μ €μž₯ 경둜 |
288
+
289
+ ---
290
+
291
+ ## npm 라이브러리 μ‚¬μš©λ²•
292
+
293
+ CI 슀크립트, μ»€μŠ€ν…€ 봇, 둜컬 도ꡬ λ“± λͺ¨λ“  Node.js ν”„λ‘œμ νŠΈμ—μ„œ 라이브러리둜 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
294
+
295
+ ### μ„€μΉ˜
296
+
297
+ ```bash
298
+ # npm
299
+ npm install github-mobile-reader
300
+
301
+ # pnpm
302
+ pnpm add github-mobile-reader
303
+
304
+ # yarn
305
+ yarn add github-mobile-reader
306
+ ```
307
+
308
+ ### CommonJS
309
+
310
+ ```js
311
+ const { generateReaderMarkdown } = require("github-mobile-reader");
312
+ ```
313
+
314
+ ### ESM / TypeScript
315
+
316
+ ```ts
317
+ import {
318
+ generateReaderMarkdown,
319
+ parseDiffToLogicalFlow,
320
+ } from "github-mobile-reader";
321
+ ```
322
+
323
+ ### κΈ°λ³Έ μ‚¬μš© μ˜ˆμ‹œ
324
+
325
+ ```ts
326
+ import { generateReaderMarkdown } from "github-mobile-reader";
327
+ import { execSync } from "child_process";
328
+ import { writeFileSync } from "fs";
329
+
330
+ // λ§ˆμ§€λ§‰ μ»€λ°‹μ˜ diff κ°€μ Έμ˜€κΈ°
331
+ const diff = execSync("git diff HEAD~1 HEAD", { encoding: "utf8" });
332
+
333
+ // 메타데이터와 ν•¨κ»˜ Reader Markdown 생성
334
+ const markdown = generateReaderMarkdown(diff, {
335
+ pr: "42",
336
+ commit: "a1b2c3d",
337
+ file: "src/api/users.ts",
338
+ repo: "my-org/my-repo",
339
+ });
340
+
341
+ // 파일 μ €μž₯ λ˜λŠ” Slack / Discord / GitHub에 κ²Œμ‹œ
342
+ writeFileSync("reader.md", markdown, "utf8");
343
+ ```
344
+
345
+ ### μ €μˆ˜μ€€ API μ˜ˆμ‹œ
346
+
347
+ 트리 ꡬ쑰만 ν•„μš”ν•œ 경우 (예: μ»€μŠ€ν…€ λ Œλ”λŸ¬ μ œμž‘):
348
+
349
+ ```ts
350
+ import { parseDiffToLogicalFlow, renderFlowTree } from "github-mobile-reader";
351
+
352
+ const { root, rawCode, removedCode } = parseDiffToLogicalFlow(diff);
353
+
354
+ // root β†’ FlowNode[] (논리 트리)
355
+ // rawCode β†’ string (μΆ”κ°€λœ 쀄, μ€„λ°”κΏˆμœΌλ‘œ μ—°κ²°)
356
+ // removedCode β†’ string (μ‚­μ œλœ 쀄, μ€„λ°”κΏˆμœΌλ‘œ μ—°κ²°)
357
+
358
+ const treeLines = renderFlowTree(root);
359
+ console.log(treeLines.join("\n"));
360
+ ```
361
+
362
+ ---
363
+
364
+ ## 좜λ ₯ ν˜•μ‹
365
+
366
+ μƒμ„±λœ Reader Markdown λ¬Έμ„œλŠ” λ„€ 개의 μ„Ήμ…˜μœΌλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€:
367
+
368
+ ````markdown
369
+ # πŸ“– GitHub Reader View
370
+
371
+ > Generated by **github-mobile-reader**
372
+ > Repository: my-org/my-repo
373
+ > Pull Request: #42
374
+ > Commit: `a1b2c3d`
375
+ > File: `src/api/users.ts`
376
+
377
+ ---
378
+
379
+ ## 🧠 Logical Flow
380
+
381
+ ```
382
+ getData()
383
+ └─ filter(callback)
384
+ └─ map(item β†’ value)
385
+ └─ reduce(callback)
386
+ ```
387
+
388
+ ## βœ… Added Code
389
+
390
+ ```typescript
391
+ const result = getData()
392
+ .filter((item) => item.active)
393
+ .map((item) => item.value)
394
+ .reduce((a, b) => a + b, 0);
395
+ ```
396
+
397
+ ## ❌ Removed Code
398
+
399
+ ```typescript
400
+ const result = getData().map((item) => item.value);
401
+ ```
402
+
403
+ ---
404
+
405
+ πŸ›  Auto-generated by github-mobile-reader. Do not edit manually.
406
+ ````
407
+
408
+ ---
409
+
410
+ ## API 레퍼런슀
411
+
412
+ ### `generateReaderMarkdown(diffText, meta?)`
413
+
414
+ 메인 μ§„μž…μ . μ›μ‹œ git diff λ¬Έμžμ—΄μ„ νŒŒμ‹±ν•΄μ„œ μ™„μ„±λœ Reader Markdown λ¬Έμ„œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
415
+
416
+ | νŒŒλΌλ―Έν„° | νƒ€μž… | μ„€λͺ… |
417
+ | ------------- | --------- | ----------------------------- |
418
+ | `diffText` | `string` | `git diff`의 μ›μ‹œ 좜λ ₯ |
419
+ | `meta.pr` | `string?` | PR 번호 |
420
+ | `meta.commit` | `string?` | 컀밋 SHA |
421
+ | `meta.file` | `string?` | 헀더에 ν‘œμ‹œν•  파일λͺ… |
422
+ | `meta.repo` | `string?` | `owner/repo` ν˜•μ‹μ˜ 레포 이름 |
423
+
424
+ **λ°˜ν™˜κ°’:** `string` β€” μ™„μ„±λœ Markdown λ¬Έμ„œ
425
+
426
+ ---
427
+
428
+ ### `parseDiffToLogicalFlow(diffText)`
429
+
430
+ λ Œλ”λ§ 없이 diffλ₯Ό κ΅¬μ‘°ν™”λœ 결과둜 νŒŒμ‹±ν•©λ‹ˆλ‹€.
431
+
432
+ **λ°˜ν™˜κ°’:** `ParseResult`
433
+
434
+ ```ts
435
+ interface ParseResult {
436
+ root: FlowNode[]; // 논리 트리 (μΆ”κ°€λœ 쀄)
437
+ rawCode: string; // μΆ”κ°€λœ 쀄 (\n으둜 μ—°κ²°)
438
+ removedCode: string; // μ‚­μ œλœ 쀄 (\n으둜 μ—°κ²°)
439
+ }
440
+ ```
441
+
442
+ ---
443
+
444
+ ### `renderFlowTree(nodes, indent?)`
445
+
446
+ `FlowNode[]` 트리λ₯Ό Markdown μ•ˆμ „ν•œ ν…μŠ€νŠΈ 쀄 λ°°μ—΄λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
447
+
448
+ ```ts
449
+ const lines = renderFlowTree(root);
450
+ // [ 'getData()', ' └─ filter(callback)', ' └─ map(item β†’ value)' ]
451
+ ```
452
+
453
+ ---
454
+
455
+ ### `FlowNode`
456
+
457
+ ```ts
458
+ interface FlowNode {
459
+ type: "root" | "chain" | "condition" | "loop" | "function" | "call";
460
+ name: string;
461
+ children: FlowNode[];
462
+ depth: number;
463
+ priority: Priority;
464
+ }
465
+ ```
466
+
467
+ ---
468
+
469
+ ### `Priority` (μ—΄κ±°ν˜•)
470
+
471
+ | κ°’ | 의미 |
472
+ | ----------------- | ----------------------------------------------- |
473
+ | `CHAINING = 1` | λ©”μ„œλ“œ 체인 (`.map()`, `.filter()`, …) β€” μ΅œμš°μ„  |
474
+ | `CONDITIONAL = 2` | `if` / `else` / `switch` 블둝 |
475
+ | `LOOP = 3` | `for` / `while` 반볡문 |
476
+ | `FUNCTION = 4` | ν•¨μˆ˜ μ„ μ–Έ |
477
+ | `OTHER = 5` | κ·Έ μ™Έ |
478
+
479
+ ---
480
+
481
+ ## νŒŒμ„œ λ™μž‘ 원리
482
+
483
+ νŒŒμ„œλŠ” 결정둠적 νŒŒμ΄ν”„λΌμΈμœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€ β€” AI μ—†μŒ, μ™ΈλΆ€ μ˜μ‘΄μ„± μ—†μŒ.
484
+
485
+ ```
486
+ git diff ν…μŠ€νŠΈ
487
+ β”‚
488
+ β–Ό
489
+ 1. filterDiffLines() β€” + / - 쀄 뢄리, +++ / --- 헀더 제거
490
+ β”‚
491
+ β–Ό
492
+ 2. normalizeCode() β€” ; 제거, 주석 제거, 곡백 정리
493
+ β”‚
494
+ β–Ό
495
+ 3. getIndentDepth() β€” 쀑첩 레벨 계산 (2 spaces = 1 레벨)
496
+ β”‚
497
+ β–Ό
498
+ 4. parseToFlowTree() β€” μš°μ„ μˆœμœ„ μˆœμ„œλ‘œ νŒ¨ν„΄ λ§€μΉ­:
499
+ β”‚ P1 체이닝 (.map .filter .reduce …)
500
+ β”‚ P2 쑰건문 (if / else / switch)
501
+ β”‚ P3 반볡문 (for / while)
502
+ β”‚ P4 ν•¨μˆ˜ μ„ μ–Έ
503
+ β”‚
504
+ β–Ό
505
+ 5. renderFlowTree() β€” 트리 β†’ λ“€μ—¬μ“°κΈ°λœ ν…μŠ€νŠΈ μ€„λ‘œ λ³€ν™˜
506
+ β”‚
507
+ β–Ό
508
+ generateReaderMarkdown() β€” μ΅œμ’… Markdown λ¬Έμ„œ 쑰립
509
+ ```
510
+
511
+ **μ£Όμš” 섀계 κ²°μ •:**
512
+
513
+ - **보수적** β€” λΆ„λ₯˜λ˜μ§€ μ•ŠλŠ” 쀄은 잘λͺ»λœ 정보 λŒ€μ‹  쑰용히 κ±΄λ„ˆλœλ‹ˆλ‹€
514
+ - **import / export / type / interface / console.log**λŠ” λ¬΄μ‹œλ©λ‹ˆλ‹€. 흐름 이해에 κΈ°μ—¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€
515
+ - **콜백 인자 μΆ•μ•½** β€” 본문이 단일 속성 접근일 λ•Œ `.map(item => item.value)`λ₯Ό `map(item β†’ value)`둜 μΆ•μ•½ν•©λ‹ˆλ‹€. κ·Έ μ™Έμ—λŠ” `map(callback)`으둜 ν‘œμ‹œν•©λ‹ˆλ‹€
516
+ - **ν•¨μˆ˜ 선언은 μ΅œμš°μ„  체크** β€” `const foo = async …`κ°€ `extractRoot`에 잘λͺ» λΆ„λ₯˜λ˜μ§€ μ•Šλ„λ‘, ν•¨μˆ˜ μ„ μ–Έ 감지λ₯Ό 루트 μΆ”μΆœλ³΄λ‹€ λ¨Όμ € μˆ˜ν–‰ν•©λ‹ˆλ‹€
517
+ - **depth**λŠ” λ“€μ—¬μ“°κΈ° 기반 (2-space κΈ°μ€€)으둜 μΆ”μ λ˜λ©°, 체이닝 감지가 μ• λ§€ν•  λ•Œ 보쑰 μ •λ³΄λ‘œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€
518
+
519
+ ### 지원 μ–Έμ–΄ (v0.1)
520
+
521
+ [μ–Έμ–΄ 지원](#μ–Έμ–΄-지원) μ„Ήμ…˜μ˜ 전체 ν‘œλ₯Ό ν™•μΈν•˜μ„Έμš”.
522
+ μš”μ•½: **JS / TS / React / Next.js μ™„μ „ 지원**, Java와 C#은 λΆ€λΆ„ 지원, C 등은 Language Adapter μ‹œμŠ€ν…œ(v0.2)으둜 κ³„νš 쀑.
523
+
524
+ ---
525
+
526
+ ## κΈ°μ—¬ν•˜κΈ°
527
+
528
+ PR은 μ–Έμ œλ“ μ§€ ν™˜μ˜ν•©λ‹ˆλ‹€! μ‹œμž‘ 방법:
529
+
530
+ ```bash
531
+ # 레포 클둠
532
+ git clone https://github.com/3rdflr/github-mobile-reader.git
533
+ cd github-mobile-reader
534
+
535
+ # μ˜μ‘΄μ„± μ„€μΉ˜
536
+ npm install
537
+
538
+ # λΉŒλ“œ (라이브러리 + Action runner)
539
+ npm run build:all
540
+
541
+ # 개발 쀑 watch λͺ¨λ“œ
542
+ npm run dev
543
+
544
+ # ν…ŒμŠ€νŠΈ μ‹€ν–‰
545
+ npx ts-node src/test.ts
546
+ ```
547
+
548
+ ### ν”„λ‘œμ νŠΈ ꡬ쑰
549
+
550
+ ```
551
+ github-mobile-reader/
552
+ β”œβ”€β”€ src/
553
+ β”‚ β”œβ”€β”€ parser.ts ← 핡심 diff β†’ logical flow νŒŒμ„œ
554
+ β”‚ β”œβ”€β”€ index.ts ← npm 곡개 API
555
+ β”‚ β”œβ”€β”€ action.ts ← GitHub Action μ§„μž…μ 
556
+ β”‚ β”œβ”€β”€ cli.ts ← CLI μ§„μž…μ  (npx github-mobile-reader)
557
+ β”‚ └── test.ts ← 슀λͺ¨ν¬ ν…ŒμŠ€νŠΈ (33개)
558
+ β”œβ”€β”€ dist/ ← 컴파일 κ²°κ³Όλ¬Ό (μžλ™ 생성, μˆ˜μ • κΈˆμ§€)
559
+ β”œβ”€β”€ reader-output/ ← CLI 좜λ ₯ 디렉토리 (gitignore됨)
560
+ β”œβ”€β”€ .github/
561
+ β”‚ └── workflows/
562
+ β”‚ └── mobile-reader.yml ← μ‚¬μš©μžμš© μ˜ˆμ‹œ μ›Œν¬ν”Œλ‘œμš°
563
+ β”œβ”€β”€ action.yml ← GitHub Action μ •μ˜
564
+ β”œβ”€β”€ README.md ← μ˜μ–΄ λ¬Έμ„œ
565
+ β”œβ”€β”€ README.ko.md ← ν•œκ΅­μ–΄ λ¬Έμ„œ (ν˜„μž¬ 파일)
566
+ β”œβ”€β”€ package.json
567
+ └── tsconfig.json
568
+ ```
569
+
570
+ ### μƒˆ μ–Έμ–΄ μ–΄λŒ‘ν„° μΆ”κ°€ν•˜κΈ°
571
+
572
+ νŒŒμ„œλŠ” ν˜„μž¬ JS/TS 문법 νœ΄λ¦¬μŠ€ν‹±μ— μ˜μ‘΄ν•©λ‹ˆλ‹€ (점 체이닝, `const`/`let`/`var`, `function`, `if`/`for`/`while`). μƒˆ μ–Έμ–΄λ₯Ό μΆ”κ°€ν•˜λ €λ©΄:
573
+
574
+ 1. `src/parser.ts`μ—μ„œ 감지 헬퍼 μΆ”κ°€ (κΈ°μ‘΄ `isChaining`, `isConditional` νŒ¨ν„΄ μ°Έκ³ )
575
+ 2. `src/action.ts`의 `filterDiffLines`μ—μ„œ μƒˆ 파일 ν™•μž₯자 ν—ˆμš©
576
+ 3. `src/test.ts`에 ν•΄λ‹Ή μ–Έμ–΄μ˜ diff μ˜ˆμ‹œλ₯Ό ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ‘œ μΆ”κ°€
577
+ 4. μ˜ˆμ‹œ diffλ₯Ό ν¬ν•¨ν•΄μ„œ PR μ˜€ν”ˆ
578
+
579
+ ---
580
+
581
+ ## λΌμ΄μ„ μŠ€
582
+
583
+ MIT Β© [3rdflr](https://github.com/3rdflr)
584
+
585
+ ---