GitHub PR diff를 모바일에서 읽기 좋은 Markdown으로 변환합니다. 긴 코드를 읽지 않아도 함수 단위로 무엇이 바뀌었는지 한눈에 파악할 수 있습니다.
GitHub 모바일 웹에서 PR을 리뷰하면 코드가 고정폭 블록으로 렌더링됩니다. 긴 줄은 가로 스크롤이 필요하고, 깊게 중첩된 로직은 한눈에 들어오지 않습니다.
github-mobile-reader는 diff를 파싱해 함수/컴포넌트 단위로 변경 내용을 요약합니다.
어떤 함수가 추가/삭제/수정됐는지, 어떤 상태나 UI가 바뀌었는지를 짧게 보여줍니다.
# PR #7 — feat: 작업 필터링 및 정렬 컨트롤 추가
owner/repo · `3f8a21c` · JS/TS 3개 파일 변경
---
## `src/components/TodoList.tsx`
> 💡 기존엔 전체 작업을 무조건 불러왔지만, 이제 filter와 sortOrder를 파라미터로 받아
> 조건부 패치로 변경됨. filter 상태 변경 시 자동으로 재패치함.
**Import 변화**
+ `FilterBar`
+ `useSortedTasks`
- `LegacyLoader` (제거됨)
**✏️ `TodoList`** _(Component)_ — 변경됨
변수: `filter`, `sortOrder`
+ (state) `filter` ← `useState({})`
~ `useEffect` deps 변경
**✏️ `fetchTasks`** _(Function)_ — 변경됨
파라미터+ `filter`
파라미터+ `sortOrder`
+ (guard) `!filter` → early return
+ (API) `api.getTasks(filter)` → `tasks`
**✅ `handleFilterChange`** _(Function)_ — 새로 추가
파라미터+ `field`
파라미터+ `value`
+ (setState) `setFilter({...filter, [field]: value})`- 함수 단위 요약 — 함수/컴포넌트별로 한 줄씩 나열.
###헤딩 없이 본문 크기 그대로 — 모바일에서 폰트 크기 정상 - 부수 효과 레이블 — 동작 줄에
(API),(setState),(state),(cond),(catch),(guard)레이블을 인라인으로 표시해 코드 안 읽고도 변화 유형 파악 가능 - guard clause 감지 —
if (!x) return패턴을(guard) early return으로 별도 표기 - Import 변화 — 파일 단위로 추가·제거된 import 감지
- 파라미터 변화 — 함수 파라미터의 추가·제거 감지 (최대 4개, 초과분은
… 외 N개) - 변수 — 단순 변수 할당을 가장 가까운 함수에 인라인으로 표시 (최대 5개)
- UI 변화 — 추가·제거된 JSX 컴포넌트 감지 (
div,span등 제네릭 태그 제외); map 및 조건부 렌더링 패턴 - Props 변화 — TypeScript 인터페이스/타입 멤버 변경 감지 (최대 5개; 긴 값은
'...'로 축약) - useEffect 중복 제거 — added/removed 양쪽에 동일한
useEffect가 나오면~ useEffect deps 변경한 줄로 통합 - cross-file 리팩토링 감지 — 같은 PR에서 A파일에서 제거되고 B파일에 추가된 심볼은
❌ 제거됨대신📦 → B파일로 이동됨으로 표시 - 오탐 방지 — diff의 context 라인에 심볼이 남아있으면
removed대신modified로 재분류 - 잘린 코드 라인 필터링 —
)로 시작하거나, 괄호 불균형이거나, 연산자로 끝나는 mid-expression 조각은 분석 전에 제거 - 빈 섹션 숨김 — 감지된 변경 내용이 없는
변경됨심볼은 출력에서 제외 - 동작 요약 우선순위 — 출력 항목을 신호 유형별로 우선순위 적용: state/API (최대 4개) → guard/catch (최대 2개) → cond (최대 2개) → setState/useEffect/return (최대 2개)
- 테스트 파일 요약 —
.test.ts등 테스트 파일은 코드 분석 대신describe/it블록 이름을 그룹별로 표시 - 설정 파일 요약 — vitest/jest/vite 설정 파일은 추가·제거된 플러그인 목록만 표시
- Gemini AI 요약 (선택) — 코드 라인이 아닌 비즈니스 로직 변화 + 부수 효과 중심 1~2줄 요약 (
> 💡 ...) - 보안 — 토큰은 환경변수로만 주입; 셸 히스토리에 노출 없음
별도 설치 없이 npx로 바로 실행합니다.
export GITHUB_TOKEN=ghp_xxxx보안 참고:
--token플래그는 지원하지 않습니다. CLI 인자로 시크릿을 전달하면 셸 히스토리와ps출력에 토큰이 노출됩니다.
npx github-mobile-reader --repo owner/repo --pr 42GEMINI_API_KEY=AIzaSy... npx github-mobile-reader --repo owner/repo --pr 42npx github-mobile-reader --repo owner/repo --all --limit 20| 플래그 | 기본값 | 설명 |
|---|---|---|
--repo |
(필수) | owner/repo 형식의 저장소 이름 |
--pr |
— | 특정 PR 번호 처리 |
--all |
— | 최근 PR 전체 처리 (--limit와 함께 사용) |
--out |
./reader-output |
출력 디렉터리 (상대 경로만, .. 불가) |
--limit |
10 |
--all 사용 시 최대 PR 수 |
--gemini-key |
— | Gemini API 키 (또는 GEMINI_API_KEY 환경변수) |
토큰: $GITHUB_TOKEN 환경변수에서 읽음 (미인증 60 req/hr, 인증 5,000 req/hr)
각 PR은 reader-output/pr-<number>.md 파일 하나를 생성합니다.
PR이 열릴 때마다 자동으로 Reader 문서를 생성하고 PR에 코멘트를 답니다.
.github/workflows/mobile-reader.yml 생성:
name: Mobile Reader
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
pull-requests: write
issues: write
jobs:
generate-reader:
name: Generate Mobile Reader View
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Generate Reader Markdown
run: npx github-mobile-reader@latest --repo ${{ github.repository }} --pr ${{ github.event.pull_request.number }} --out ./reader-output
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Post PR Comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
FILE="./reader-output/pr-${PR_NUMBER}.md"
if [ ! -f "$FILE" ]; then
echo "No reader file generated."
exit 0
fi
# 이전 봇 코멘트 삭제
PREV_ID=$(gh api repos/${{ github.repository }}/issues/${PR_NUMBER}/comments \
--jq '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith("# PR #"))) | .id' \
| head -1)
if [ -n "$PREV_ID" ]; then
gh api -X DELETE repos/${{ github.repository }}/issues/comments/${PREV_ID}
fi
gh pr comment ${PR_NUMBER} --repo ${{ github.repository }} --body-file "$FILE"이후 모든 PR에 자동으로 요약 코멘트가 게시됩니다.
useCanvasRenderer 같은 200줄짜리 훅도 1~3줄로 자연어 요약해줍니다.
API 키가 없으면 오류 없이 기존과 동일하게 동작합니다.
Gemini 2.5 Flash Lite 사용 — 빠르고 저렴하며 thinking 오버헤드 없음.
aistudio.google.com/apikey — 무료로 발급 가능
# 환경변수로 전달 (권장)
GEMINI_API_KEY=AIzaSy... npx github-mobile-reader --repo owner/repo --pr 42
# 플래그로 전달
npx github-mobile-reader --repo owner/repo --pr 42 --gemini-key AIzaSy...- 저장소 Settings → Secrets and variables → Actions → New repository secret
- 이름:
GEMINI_API_KEY, 값: 발급받은 키 Generate Reader Markdown스텝에--gemini-key ${{ secrets.GEMINI_API_KEY }}추가
보안: GitHub Secrets에 저장된 키는 워크플로우 로그에서도 마스킹되어 절대 노출되지 않습니다.
# PR #7 — feat: 작업 필터링 및 정렬 컨트롤 추가
owner/repo · `3f8a21c` · JS/TS 3개 파일 변경
---
## `src/components/TodoList.tsx`
> 💡 기존엔 전체 작업을 무조건 불러왔지만, 이제 filter와 sortOrder를 파라미터로 받아
> 조건부 패치로 변경됨. filter 상태 변경 시 자동으로 재패치함.
**Import 변화**
+ `FilterBar`
+ `useSortedTasks`
- `LegacyLoader` (제거됨)
**✏️ `TodoList`** _(Component)_ — 변경됨
변수: `filter`, `sortOrder`
+ (state) `filter` ← `useState({})`
~ `useEffect` deps 변경
**✏️ `fetchTasks`** _(Function)_ — 변경됨
파라미터+ `filter`
파라미터+ `sortOrder`
+ (guard) `!filter` → early return
+ (API) `api.getTasks(filter)` → `tasks`
**✅ `handleFilterChange`** _(Function)_ — 새로 추가
파라미터+ `field`
파라미터+ `value`
+ (setState) `setFilter({...filter, [field]: value})`
**✏️ `TaskCard`** _(Component)_ — 변경됨
Props+ `dueDate: '...'`
+ (cond) `!task.completed`
UI: `<Badge>`| 표시 | 의미 |
|---|---|
✅ ... — 새로 추가 |
diff에서 새로 등장한 함수/컴포넌트 |
❌ ... — 제거됨 |
diff에서 완전히 사라진 함수/컴포넌트 |
✏️ ... — 변경됨 |
기존에 존재하고 내용이 수정된 함수/컴포넌트 |
📦 ... — 이동됨 |
같은 PR 내 다른 파일에서 제거되고 이 파일에 추가된 심볼 |
변수: x, y |
단순 변수 할당을 인라인으로 축약 표시 (최대 5개, 초과분은 외 N개) |
| Prefix | 의미 |
|---|---|
+ |
추가된 동작 |
- |
제거된 동작 |
~ |
변경된 동작 (added/removed 양쪽에 같은 신호가 있을 때, 예: useEffect deps 변경) |
(API) |
await 호출 — 서버나 외부 서비스에서 데이터 패치 |
(setState) |
setState 호출 — React 상태 업데이트 |
(state) |
훅 할당 — const x = useHook() |
(cond) |
if / else if 분기 |
(guard) |
guard clause — if (!x) return 조기 종료 패턴 |
(catch) |
catch 블록 |
(return) |
비자명 반환값 |
파라미터+ / 파라미터- |
함수 파라미터 추가 / 제거 (최대 4개) |
Props+ / Props- |
TypeScript 인터페이스/타입 멤버 추가 / 제거 (최대 5개) |
UI: |
JSX 컴포넌트 추가 또는 제거 |
npm install github-mobile-readerimport { generateReaderMarkdown } from 'github-mobile-reader';
import { execSync } from 'child_process';
const diff = execSync('git diff HEAD~1 HEAD', { encoding: 'utf8' });
const markdown = generateReaderMarkdown(diff, {
pr: '42',
commit: 'a1b2c3d',
file: 'src/api/users.ts',
repo: 'my-org/my-repo',
});
console.log(markdown);import {
generateReaderMarkdown, // diff → 완성된 Markdown 문서
parseDiffHunks, // diff → DiffHunk[]
attributeLinesToSymbols, // DiffHunk[] → SymbolDiff[]
generateSymbolSections, // SymbolDiff[] → string[]
extractImportChanges, // 추가·제거된 import 감지
extractParamChanges, // 추가·제거된 함수 파라미터 감지
extractRemovedSymbolNames, // diff → string[] (순수 제거된 심볼 이름)
extractAddedSymbolNames, // diff → string[] (순수 추가된 심볼 이름)
} from 'github-mobile-reader';| 파라미터 | 타입 | 설명 |
|---|---|---|
diffText |
string |
git diff 원시 출력 |
meta.pr |
string? |
PR 번호 |
meta.commit |
string? |
커밋 SHA |
meta.file |
string? |
파일 이름 (테스트/설정 파일 감지에 사용) |
meta.repo |
string? |
owner/repo 형식 |
meta.movedOutMap |
Map<string, string>? |
심볼 → 이동된 대상 파일 (cross-file 이동 표시) |
meta.movedIntoThisFile |
Set<string>? |
이 파일로 이동된 심볼 이름 목록 |
반환값: string — 완성된 Markdown 문서
interface SymbolDiff {
name: string;
kind: 'component' | 'function' | 'setup';
status: 'added' | 'removed' | 'modified' | 'moved';
addedLines: string[];
removedLines: string[];
movedTo?: string; // status === 'moved'일 때 이동된 대상 파일
movedFrom?: string; // status === 'moved'일 때 이동된 원본 파일
}파서는 JS/TS 구문 패턴에 최적화되어 있습니다.
| 언어 | 확장자 | 지원 수준 |
|---|---|---|
| JavaScript | .js .mjs .cjs |
완전 지원 |
| TypeScript | .ts |
완전 지원 |
| React JSX | .jsx |
완전 지원 |
| React TSX | .tsx |
완전 지원 |
| 기타 언어 | — | 예정 |
github-mobile-reader/
├── src/
│ ├── parser.ts ← diff 파싱 및 심볼 분석 (핵심 로직)
│ ├── gemini.ts ← Gemini 2.5 Flash Lite AI 요약 (opt-in)
│ ├── index.ts ← npm 공개 API
│ ├── action.ts ← GitHub Action 진입점
│ └── cli.ts ← CLI 진입점 (2-pass cross-file 분석)
├── dist/ ← 컴파일 결과물 (자동 생성)
├── reader-output/ ← CLI 출력 디렉터리 (gitignored)
├── action.yml ← GitHub Action 정의
└── package.json
git clone https://github.com/3rdflr/github-mobile-reader.git
cd github-mobile-reader
npm install
npm run build:all # 라이브러리 + Action + CLI 빌드PR 환영합니다.
MIT © 3rdflr