RepoPilot์ ๋ก์ปฌ ํ๊ฒฝ์์ ์คํ๋๋ Rust CLI ๋๊ตฌ๋ก, ์๋ ๋์์ ๋ฉํฐ ์์ด์ ํธ๋ก ์ฝ๋ ๋ฆฌ๋ทฐํฉ๋๋ค.
- GitHub Pull Request
- GitLab Merge Request
์ฌ์ฉ์๋ PR/MR URL ํ๋๋ง ์ ๋ ฅํ๋ฉด ๋๊ณ , ํ๋ซํผ(GitHub/GitLab)์ ์๋์ผ๋ก ๊ฐ์ง๋ฉ๋๋ค.
- URL๋ง์ผ๋ก ์คํ:
repopilot "<PR_OR_MR_URL>" - GitHub/GitLab ์๋ ๊ฐ์ง
- ๋ฉํฐ ํ๋ก๋ฐ์ด๋ ๋ฆฌ๋ทฐ (Codex, Claude, Gemini)
- API ํค ๋์ ๋ก์ปฌ์ ์ค์น/๋ก๊ทธ์ธ๋ CLI ๋ช ๋ น ์คํ
- API ๊ธฐ๋ฐ diff ์กฐํ (๋ก์ปฌ checkout ๋ถํ์)
- claim/final ๋ง์ปค ๊ธฐ๋ฐ ์ค๋ณต ์คํ ๋ฐฉ์ง
- ์์ด์ ํธ๋ณ ๊ฐ๋ณ ์ฝ๋ฉํธ ์์ฑ + ์ต์ข ์์ฝ ์ฝ๋ฉํธ ์์ฑ
- ์ต์ข ์์ฝ ์ฝ๋ฉํธ์ "์์ด์ ํธ ๊ฐ ์ํธ ์๊ฒฌ" ํฌํจ
- ์ฌ๋ฌ config ํ์ผ ๊ฒฝ๋ก ๋ณํฉ/๋ฎ์ด์ฐ๊ธฐ ์ง์
- ํ์ฌ ์ ์ฉ config ํ์ธ ๋ช
๋ น:
repopilot config
RepoPilot์ **Clean Architecture + DDD + Hexagonal Architecture(Ports & Adapters)**๋ฅผ ํจ๊ป ์ ์ฉํฉ๋๋ค.
ํต์ฌ ์์น:
domain์ ๊ฐ์ฅ ์์ชฝ(inside core)์ด๋ฉฐ ์ธ๋ถ ๊ธฐ์ ์ ๋ชจ๋ฆ ๋๋ค.application์ ์ ์ค์ผ์ด์ค์ ํฌํธ(์ธํฐํ์ด์ค)๋ฅผ ์์ ํฉ๋๋ค.interface๋ ์ธ๋ฐ์ด๋ ์ด๋ํฐ(์ฌ์ฉ์ ์ ๋ ฅ)์ ๋๋ค.infrastructure๋ ์์๋ฐ์ด๋ ์ด๋ํฐ(์ธ๋ถ API/CLI/ํ์ผ)์ ๋๋ค.- ์์กด์ฑ ๋ฐฉํฅ์ ์์ชฝ์ผ๋ก๋ง ํฅํฉ๋๋ค.
์์กด์ฑ ๊ท์น:
- ํ์ฉ:
interface -> application,application -> domain,infrastructure -> application(ports), domain - ๊ธ์ง:
application -> infrastructure/interface,domain -> application/infrastructure/interface
ํ์ฌ ์ฝ๋ ๋งคํ:
src/domain- ์ํฐํฐ/๊ฐ ๊ฐ์ฒด/๋๋ฉ์ธ ์ ์ฑ
- ์: SHA ๋ง์ปค ์ ์ฑ , ๊ต์ฐจ ์์ด์ ํธ ํ๋กฌํํธ ์ ์ฑ , ์ฌ์ฉ๋ ์ง๊ณ ์ ์ฑ
src/application- ์ ์ค์ผ์ด์ค + ํฌํธ
- ์:
usecases/review_pr/*,ports.rs
src/interface- ์ธ๋ฐ์ด๋ ์ธํฐํ์ด์ค ๊ณ์ธต
- ํ์ฌ๋ CLI ์ธํฐํ์ด์ค๋ง ๊ตฌํ:
src/interface/cli/* - ์:
src/interface/cli/command.rs,src/interface/cli/repl.rs,src/interface/cli/composition.rs
src/infrastructure- ์์๋ฐ์ด๋ ์ด๋ํฐ ๊ตฌํ
- ์:
vcs/*,providers/*,config/*,render.rs,adapters/*
RepoPilot provider๋ ๋ ๊ฐ์ง ์คํ ๋ชจ๋๋ฅผ ์ง์ํฉ๋๋ค.
- API ๋ชจ๋(๊ถ์ฅ): provider๋ณ
api_key๋๋api_key_env์ค์ (CLI ์ค์น ๋ถํ์) - CLI ๋ชจ๋: ๋ก์ปฌ ๋ฐ์ด๋๋ฆฌ(
codex/claude/gemini) ์ค์น + ๋ก๊ทธ์ธ(OAuth)
๋์ ์ฐ์ ์์: API key๊ฐ ์์ผ๋ฉด API ๋ชจ๋, ์์ผ๋ฉด CLI ๋ชจ๋.
VCS ์ฝ๋ฉํธ/๋ ธํธ ์์ฑ์๋ host ํ ํฐ์ด ํ์ํฉ๋๋ค.
- PAT(๊ฐ๋จ):
GITHUB_TOKEN/GITLAB_TOKENํ๊ฒฝ๋ณ์ ๋๋hosts.<host>.token์ค์ - OAuth(๊ถ์ฅ):
gh/glab์ค์น ํ ๋ก๊ทธ์ธrepopilot auth github(GitHub:gh auth login)repopilot auth gitlab(GitLab:glab auth login)
gh/glab ์ค์น ์์:
- macOS(Homebrew):
brew install gh glab - Windows(winget):
winget install --id GitHub.cli/winget install --id glab.glab - Linux: ๋ฐฐํฌํ ํจํค์ง ๋งค๋์ ๋ก ์ค์น(๋๋ ๊ณต์ ๋ฆด๋ฆฌ์ฆ ๋ฐ์ด๋๋ฆฌ)
API ํค๋ฅผ ์ฐ์ง ์๊ณ ๋ก์ปฌ CLI๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด provider CLI ์ค์น์ ๋ก๊ทธ์ธ์ด ํ์ํ ์ ์์ต๋๋ค.
- Codex:
codex(install:npm install -g @openai/codex)- ๋ก๊ทธ์ธ:
repopilot auth codex(๋ด๋ถ์ ์ผ๋กcodex login)
- ๋ก๊ทธ์ธ:
- Claude Code:
claude(install:npm install -g @anthropic-ai/claude-code)- ๋ก๊ทธ์ธ:
repopilot auth claude(๋ด๋ถ์ ์ผ๋กclaude auth login)
- ๋ก๊ทธ์ธ:
- Gemini CLI:
gemini(install:npm install -g @google/gemini-cli)- ๋ก๊ทธ์ธ:
repopilot auth gemini(CLI ์คํ ํ Login with Google)
- ๋ก๊ทธ์ธ:
์ฐธ๊ณ :
- Node.js๊ฐ ์์ผ๋ฉด
npm์ค์น ๋ฐฉ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค(๋์ brew/winget ๋ฑ ์ฌ์ฉ). - API ํค ๋ชจ๋๋ผ๋ฉด provider CLI๊ฐ ์์ด๋ ๋ฉ๋๋ค:
OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY.
cargo build --release์์ค์์ ๋ฐ๋ก ์คํ:
cargo run --bin repopilot -- "https://github.com/org/repo/pull/123" --dry-run๋น๋๋ ๋ฐ์ด๋๋ฆฌ ์คํ:
./target/release/repopilot "https://github.com/org/repo/pull/123"๋ฌ๋๊ฐ ์์ด๋ ๋ก์ปฌ ๋จธ์ ์์ ์ง์ ๋ฐฐํฌํ ์ ์์ต๋๋ค.
scripts/ ์๋ ์คํฌ๋ฆฝํธ๊ฐ ๋ก์ปฌ ํ๊ทธ ํธ์ -> ๋น๋ -> Package ์
๋ก๋ -> Release ์์ฑ/์
๋ฐ์ดํธ๊น์ง ์ฒ๋ฆฌํฉ๋๋ค.
GITLAB_TOKEN=<YOUR_TOKEN> \
scripts/publish-gitlab.sh \
--project-id <PROJECT_ID> \
--tag v0.1.0 \
--gitlab-url https://gitlab.your-company.com์ต์ :
- ํ๊ทธ ํธ์๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด
--no-tag-push - Release ์์ฑ/์
๋ฐ์ดํธ๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด
--no-release
$env:GITLAB_TOKEN=\"<YOUR_TOKEN>\"
.\\scripts\\publish-gitlab.ps1 `
-ProjectId <PROJECT_ID> `
-Tag v0.1.0 `
-GitLabUrl https://gitlab.your-company.comWindows ๋ฐฐํฌ ์คํฌ๋ฆฝํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๊ทธ๋ฅผ ์์ฑ/ํธ์ํฉ๋๋ค.
- ํ๊ทธ ํธ์๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด
-NoTagPush - Release ์์ฑ/์
๋ฐ์ดํธ๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด
-NoRelease
macOS/Linux:
scripts/install-gitlab.sh \
--project-id <PROJECT_ID> \
--tag v0.1.0 \
--gitlab-url https://gitlab.your-company.com \
--token <YOUR_TOKEN>Windows:
.\\scripts\\install-gitlab.ps1 `
-ProjectId <PROJECT_ID> `
-Tag v0.1.0 `
-GitLabUrl https://gitlab.your-company.com `
-Token <YOUR_TOKEN>์ค์น ํ ์ด๋ ๊ฒฝ๋ก์์๋ ์๋์ฒ๋ผ ์คํ๋ฉ๋๋ค.
repopilot --help๊ธฐ๋ณธ ๋ช ๋ น:
repopilot "<PR_OR_MR_URL>"๋ํํ ๋ชจ๋(์ฌ๋์ ์ปค๋งจ๋):
repopilot๋ํํ ๋ชจ๋ ์์ ์ ์ํ ๋์๋ณด๋๊ฐ ๋จผ์ ์ถ๋ ฅ๋ฉ๋๋ค.
- config ๋ก๋ฉ ์ํ
- host/token ํด์ ์ํ
- provider๋ณ ์คํ ๋ชจ๋(api/cli)์ ์คํ ๊ฐ๋ฅ ์ฌ๋ถ
- review guide ๊ฒฝ๋ก
- comment language
๋ํํ ๋ช ๋ น:
/๋ก ์ ๋ ฅ์ ์์ํ๋ฉด ์ค์๊ฐ ๋ช ๋ น ์ถ์ฒ ํ์ (๋ฐฉํฅํค ์ด๋ + Tab ์๋์์ฑ + Enter ์คํ)/config/review <PR_OR_MR_URL> [--dry-run] [--force]/exit๋๋/quit
์์:
repopilot "https://github.com/org/repo/pull/123"
repopilot "https://gitlab.com/group/subgroup/repo/-/merge_requests/45"์ต์ :
--dry-run: ์ต์ข Markdown๋ง stdout์ ์ถ๋ ฅํ๊ณ ์ฝ๋ฉํธ/๋ ธํธ๋ ์์ฑํ์ง ์์--force: ํ์ฌ HEAD SHA์ ๋ํด ์ด๋ฏธ claim/review๊ฐ ์์ด๋ ๊ฐ์ ๋ก ์ฌ์คํ
์ต์ด ์คํ ์ ์ค์ ํ์ผ์ด ์์ผ๋ฉด ์๋ ํ ํ๋ฆฟ์ด ์๋ ์์ฑ๋ฉ๋๋ค.
./.repopilot/config.json./.repopilot/review-guide.md
์คํ ํ๋ฆ: 0. ์ํ ๋์๋ณด๋ ์ถ๋ ฅ
- claim ์ฝ๋ฉํธ ์์ฑ/์ ๋ฐ์ดํธ
- ๊ฐ ์์ด์ ํธ 1์ฐจ ๋ฆฌ๋ทฐ ์คํ
- ์์ด์ ํธ๋ณ ๊ฐ๋ณ ์ฝ๋ฉํธ ์์ฑ/์ ๋ฐ์ดํธ
- ๊ฐ ์์ด์ ํธ๊ฐ ๋ค๋ฅธ ์์ด์ ํธ ์๊ฒฌ์ ๋ํ 2์ฐจ ์ฝ๋ฉํธ ์์ฑ
- claim ์ฝ๋ฉํธ๋ฅผ ์ต์ข ์์ฝ ์ฝ๋ฉํธ๋ก ์ ๋ฐ์ดํธ
defaults.comment_language์ค์ ๊ฐ์ผ๋ก ์์ด์ ํธ ์๋ต ์ธ์ด๋ฅผ ํต์ผ
์ํ ๋์๋ณด๋์๋ ์๋๊ฐ ํฌํจ๋ฉ๋๋ค.
- Config ์ ์ ๋ก๋ฉ ์ฌ๋ถ
- Target Host
- Host Token ํด์ ์ฌ๋ถ ๋ฐ API ์ ๊ทผ ๊ฒ์ฆ ๊ฒฐ๊ณผ
- Provider๋ณ enabled/mode(api|cli)/์คํ ๊ฐ๋ฅ ์ฌ๋ถ
review_guide_path๋ฐ ํ์ผ ์กด์ฌ ์ฌ๋ถcomment_language
RepoPilot์ ์๋ ์์๋ก JSON config ํ์ผ์ ์ฝ๊ณ ๋ณํฉํฉ๋๋ค.
(๋ฎ์ ์ฐ์ ์์ -> ๋์ ์ฐ์ ์์)
/etc/repopilot/config.json~/.config/repopilot/config.json(OS ํ์ค config ๋๋ ํฐ๋ฆฌ)./.repopilot/config.json(recommended)REPOPILOT_CONFIG=/path/to/config.json(์ต์ฐ์ )
๋ค์์ ์ฝ์ ํ์ผ์ ๊ฐ์ด ์์ ๊ฐ์ ๋ฎ์ด์๋๋ค.
{
"defaults": {
"max_diff_bytes": 120000,
"system_prompt": "You are a strict senior code reviewer. Output Markdown with sections: Critical, Major, Minor, Suggestions.",
"review_guide_path": ".repopilot/review-guide.md",
"comment_language": "ko",
"update_check_url": "https://gitlab.your-company.com/api/v4/projects/<PROJECT_ID>/releases/permalink/latest",
"update_download_url": "https://gitlab.your-company.com/your-group/your-project/-/releases",
"update_timeout_ms": 1200
},
"hosts": {
"github.com": {
"token_env": "GITHUB_TOKEN",
"token_command": ["gh", "auth", "token"]
},
"gitlab.com": {
"token_env": "GITLAB_TOKEN",
"token_command": ["glab", "auth", "token"]
}
},
"providers": {
"openai": {
"enabled": true,
"api_key_env": "OPENAI_API_KEY",
"model": "gpt-4.1-mini",
"command": "codex",
"args": ["exec"],
"auto_auth": true,
"auth_command": ["codex", "login"]
},
"anthropic": {
"enabled": true,
"api_key_env": "ANTHROPIC_API_KEY",
"model": "claude-3-7-sonnet-latest",
"command": "claude",
"use_stdin": false,
"args": ["-p", "{prompt}"],
"auto_auth": true,
"auth_command": ["claude", "auth", "login"]
},
"gemini": {
"enabled": true,
"api_key_env": "GEMINI_API_KEY",
"model": "gemini-2.0-flash",
"command": "gemini",
"use_stdin": false,
"args": ["-p", "{prompt}"],
"auto_auth": true,
"auth_command": ["gemini"]
}
}
}enabled: provider ์ฌ์ฉ ์ฌ๋ถ (true/false)api_key/api_key_env: API ์ธ์ฆ ํค(๋๋ OAuth access token) ๊ฐ/ํ๊ฒฝ๋ณ์api_base(์ ํ): API ๋ฒ ์ด์ค URL overridemodel(์ ํ): provider ๊ธฐ๋ณธ ๋ชจ๋ธ IDcommand: CLI ๋ชจ๋์์ ์คํํ ๋ก์ปฌ ๋ช ๋ น ์ด๋ฆ ๋๋ ๊ฒฝ๋กargs: CLI ๋ชจ๋ ๋ช ๋ น ์ธ์ ๋ฐฐ์ดuse_stdin(์ ํ): CLI ๋ชจ๋์์ ํ๋กฌํํธ ์ ๋ฌ ์ ๊ธฐ๋ณธ๊ฐtrueauto_auth(์ ํ): CLI ๋ชจ๋์์ ์ธ์ฆ ์ค๋ฅ ๊ฐ์ง ์auth_command๋ฅผ 1ํ ์คํ ํ ์ฌ์๋(๊ธฐ๋ณธtrue, TTY์์๋ง ๋์)auth_command(์ ํ): OAuth/๋ก๊ทธ์ธ์ฉ ์ปค๋งจ๋ ๋ฐฐ์ด(์:["codex","login"],["claude","auth","login"],["gemini"])defaults.review_guide_path: ๋ฆฌ๋ทฐ ์ง์นจ Markdown ํ์ผ ๊ฒฝ๋ก. ๋ด์ฉ์ด system prompt์ ์ถ๊ฐ๋จdefaults.comment_language: ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ ์ธ์ด (ko๋๋en, ๊ธฐ๋ณธ๊ฐko)defaults.update_check_url: ์ต์ ๋ฒ์ ํ์ธ endpoint (plain text ๋ฒ์ ๋ฌธ์์ด ๋๋ JSON)defaults.update_download_url: ์ ๋ฐ์ดํธ ์๋ด์ ์ถ๋ ฅํ ๋ค์ด๋ก๋ URL (์ ํ)defaults.update_timeout_ms: ์ ๋ฐ์ดํธ ์ฒดํฌ ํ์์์(ms, ๊ธฐ๋ณธ1200)
์ถ๊ฐ ๊ท์น:
api_key๋๋api_key_env๊ฐ ์ค์ ๋๋ฉด API ๋ชจ๋๊ฐ ์ฐ์ ์ฌ์ฉ๋จ- API ํค๊ฐ ์์ ๋๋ง CLI ๋ชจ๋(
command/args)๋ฅผ ์ฌ์ฉํจ use_stdin=false์ผ ๋args์์{prompt}๊ฐ ์์ผ๋ฉด ์นํํด์ ์ ๋ฌuse_stdin=false์ด๊ณ{prompt}๊ฐ ์์ผ๋ฉด ํ๋กฌํํธ ๋ฌธ์์ด์ ๋ง์ง๋ง ์ธ์๋ก ์๋ ์ถ๊ฐ
repopilot config์ถ๋ ฅ(JSON)์๋ ๋ค์ ์ ๋ณด๊ฐ ํฌํจ๋ฉ๋๋ค.
- ํ์ํ config ๊ฒฝ๋ก ๋ชฉ๋ก (
searched_paths) - ์ค์ ๋ก๋๋ ๊ฒฝ๋ก ๋ชฉ๋ก (
loaded_paths) - ์๋ณธ defaults (
defaults) - ํด๋ฐฑ ํฌํจ ์ต์ข
defaults (
effective_defaults) - host๋ณ ํ ํฐ ์์ค/ํด๊ฒฐ ์ฌ๋ถ
- provider๋ณ resolved mode(api/cli), runnable ์ฌ๋ถ, command/args/use_stdin ์ ๋ณด
ํน์ ํ์ผ๋ก ๊ฐ์ ํ ์คํธ:
REPOPILOT_CONFIG=/tmp/config.json repopilot config๊ฐ ๋์์ HEAD SHA๋ง๋ค ๊ธฐ์กด ์ฝ๋ฉํธ/๋ ธํธ์์ ์๋ ๋ง์ปค๋ฅผ ํ์ธํฉ๋๋ค.
- final marker:
<!-- repopilot-bot sha=<SHA> --> - claim marker:
<!-- repopilot-bot claim sha=<SHA> -->
๋์ ์์:
- ํ์ฌ HEAD SHA ์กฐํ
- ๋์ผ SHA์ ๋ง์ปค๊ฐ ์ด๋ฏธ ์์ผ๋ฉด ์คํต (
--force๋ฉด ์งํ) - ์์ผ๋ฉด claim ์ฝ๋ฉํธ/๋ ธํธ ์์ฑ ๋๋ ์ ๋ฐ์ดํธ
- provider๋ค์ ๋ณ๋ ฌ๋ก ์คํ
- claim ์ฝ๋ฉํธ/๋ ธํธ๋ฅผ ์ต์ข ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ๋ก ์ ๋ฐ์ดํธ
- ์ค์ ์ฝ๋ฉํธ ์์ฑ์๋ ํด๋น host์ VCS ํ ํฐ์ด ํ์ํฉ๋๋ค.
--dry-run์ ์ฝ๋ฉํธ ์์ฑ์ ํ์ง ์์ง๋ง, private ์ ์ฅ์์์๋ API ์ฝ๊ธฐ ๊ถํ์ด ์ฌ์ ํ ํ์ํ ์ ์์ต๋๋ค.- diff๊ฐ
defaults.max_diff_bytes๋ฅผ ์ด๊ณผํ๋ฉด ์๋ฆฌ๊ณ... (diff truncated)๋ฌธ๊ตฌ๊ฐ ์ถ๊ฐ๋ฉ๋๋ค. - API key๊ฐ ์ค์ ๋์ง ์์๊ณ provider ์ปค๋งจ๋๊ฐ PATH์์ ๋ฐ๊ฒฌ๋์ง ์์ผ๋ฉด ํด๋น provider๋ ์๋ ์ ์ธ๋ฉ๋๋ค.
- ์ผ๋ถ CLI๊ฐ
stdin is not a terminal์ค๋ฅ๋ฅผ ๋ด๋ฉด CLI ๋ชจ๋์์ stdin ์๋ ๋ฐฉ์์ผ๋ก 1ํ ์ฌ์๋ํฉ๋๋ค. - 1์ฐจ ๋ฆฌ๋ทฐ/์ํธ ์ฝ๋ฉํธ ํ๋กฌํํธ๋ ์์ด๋ก ๊ตฌ์ฑ๋๋ฉฐ, ์ต์ข
์ถ๋ ฅ ์ธ์ด๋
defaults.comment_language๊ฐ์ผ๋ก ์ ์ด๋ฉ๋๋ค. defaults.update_check_url์ด ์ค์ ๋์ด ์์ผ๋ฉด ์คํ ์์ ์ ์ต์ ๋ฒ์ ์ด ์๋์ง ํ์ธํ๊ณ , ์ ๋ฒ์ ์ด ์์ผ๋ฉด ์ ๋ฐ์ดํธ ์๋ด๋ฅผ ์ถ๋ ฅํฉ๋๋ค.