# Contributing to OmniRoute (ํ•œ๊ตญ์–ด) ๐ŸŒ **Languages:** ๐Ÿ‡บ๐Ÿ‡ธ [English](../../../CONTRIBUTING.md) ยท ๐Ÿ‡ช๐Ÿ‡น [am](../am/CONTRIBUTING.md) ยท ๐Ÿ‡ธ๐Ÿ‡ฆ [ar](../ar/CONTRIBUTING.md) ยท ๐Ÿ‡ฆ๐Ÿ‡ฟ [az](../az/CONTRIBUTING.md) ยท ๐Ÿ‡ง๐Ÿ‡ฌ [bg](../bg/CONTRIBUTING.md) ยท ๐Ÿ‡ง๐Ÿ‡ฉ [bn](../bn/CONTRIBUTING.md) ยท ๐Ÿ‡ง๐Ÿ‡ฆ [bs](../bs/CONTRIBUTING.md) ยท ๐Ÿ‡จ๐Ÿ‡ฟ [cs](../cs/CONTRIBUTING.md) ยท ๐Ÿ‡ฉ๐Ÿ‡ฐ [da](../da/CONTRIBUTING.md) ยท ๐Ÿ‡ฉ๐Ÿ‡ช [de](../de/CONTRIBUTING.md) ยท ๐Ÿ‡ฌ๐Ÿ‡ท [el](../el/CONTRIBUTING.md) ยท ๐Ÿ‡ช๐Ÿ‡ธ [es](../es/CONTRIBUTING.md) ยท ๐Ÿ‡ช๐Ÿ‡ช [et](../et/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ท [fa](../fa/CONTRIBUTING.md) ยท ๐Ÿ‡ซ๐Ÿ‡ฎ [fi](../fi/CONTRIBUTING.md) ยท ๐Ÿ‡ซ๐Ÿ‡ท [fr](../fr/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ช [ga](../ga/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [gu](../gu/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ฌ [ha](../ha/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ฑ [he](../he/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [hi](../hi/CONTRIBUTING.md) ยท ๐Ÿ‡ญ๐Ÿ‡ท [hr](../hr/CONTRIBUTING.md) ยท ๐Ÿ‡ญ๐Ÿ‡บ [hu](../hu/CONTRIBUTING.md) ยท ๐Ÿ‡ฆ๐Ÿ‡ฒ [hy](../hy/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ฉ [id](../id/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ฌ [ig](../ig/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡น [it](../it/CONTRIBUTING.md) ยท ๐Ÿ‡ฏ๐Ÿ‡ต [ja](../ja/CONTRIBUTING.md) ยท ๐Ÿ‡ฌ๐Ÿ‡ช [ka](../ka/CONTRIBUTING.md) ยท ๐Ÿ‡ฐ๐Ÿ‡ญ [km](../km/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [kn](../kn/CONTRIBUTING.md) ยท ๐Ÿ‡ฑ๐Ÿ‡น [lt](../lt/CONTRIBUTING.md) ยท ๐Ÿ‡ฑ๐Ÿ‡ป [lv](../lv/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [ml](../ml/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [mr](../mr/CONTRIBUTING.md) ยท ๐Ÿ‡ฒ๐Ÿ‡พ [ms](../ms/CONTRIBUTING.md) ยท ๐Ÿ‡ฒ๐Ÿ‡น [mt](../mt/CONTRIBUTING.md) ยท ๐Ÿ‡ฒ๐Ÿ‡ฒ [my](../my/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ต [ne](../ne/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ฑ [nl](../nl/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ด [no](../no/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [or](../or/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [pa](../pa/CONTRIBUTING.md) ยท ๐Ÿ‡ต๐Ÿ‡ญ [phi](../phi/CONTRIBUTING.md) ยท ๐Ÿ‡ต๐Ÿ‡ฑ [pl](../pl/CONTRIBUTING.md) ยท ๐Ÿ‡ต๐Ÿ‡น [pt](../pt/CONTRIBUTING.md) ยท ๐Ÿ‡ง๐Ÿ‡ท [pt-BR](../pt-BR/CONTRIBUTING.md) ยท ๐Ÿ‡ท๐Ÿ‡ด [ro](../ro/CONTRIBUTING.md) ยท ๐Ÿ‡ท๐Ÿ‡บ [ru](../ru/CONTRIBUTING.md) ยท ๐Ÿ‡ฑ๐Ÿ‡ฐ [si](../si/CONTRIBUTING.md) ยท ๐Ÿ‡ธ๐Ÿ‡ฐ [sk](../sk/CONTRIBUTING.md) ยท ๐Ÿ‡ธ๐Ÿ‡ฎ [sl](../sl/CONTRIBUTING.md) ยท ๐Ÿ‡ท๐Ÿ‡ธ [sr](../sr/CONTRIBUTING.md) ยท ๐Ÿ‡ธ๐Ÿ‡ช [sv](../sv/CONTRIBUTING.md) ยท ๐Ÿ‡ฐ๐Ÿ‡ช [sw](../sw/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [ta](../ta/CONTRIBUTING.md) ยท ๐Ÿ‡ฎ๐Ÿ‡ณ [te](../te/CONTRIBUTING.md) ยท ๐Ÿ‡น๐Ÿ‡ญ [th](../th/CONTRIBUTING.md) ยท ๐Ÿ‡น๐Ÿ‡ท [tr](../tr/CONTRIBUTING.md) ยท ๐Ÿ‡บ๐Ÿ‡ฆ [uk-UA](../uk-UA/CONTRIBUTING.md) ยท ๐Ÿ‡ต๐Ÿ‡ฐ [ur](../ur/CONTRIBUTING.md) ยท ๐Ÿ‡บ๐Ÿ‡ฟ [uz](../uz/CONTRIBUTING.md) ยท ๐Ÿ‡ป๐Ÿ‡ณ [vi](../vi/CONTRIBUTING.md) ยท ๐Ÿ‡ณ๐Ÿ‡ฌ [yo](../yo/CONTRIBUTING.md) ยท ๐Ÿ‡จ๐Ÿ‡ณ [zh-CN](../zh-CN/CONTRIBUTING.md) ยท ๐Ÿ‡น๐Ÿ‡ผ [zh-TW](../zh-TW/CONTRIBUTING.md) --- Thank you for your interest in contributing! This guide covers everything you need to get started. --- ## ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ • ### ์‚ฌ์ „ ์š”๊ตฌ ์‚ฌํ•ญ - **Node.js** `>=22.22.3 <23` ๋˜๋Š” `>=24.0.0 <27`(๊ถŒ์žฅ: 24 LTS) - **npm** 10+ > **npm v11+ ์‚ฌ์šฉ์ž(Node 24+):** `npm install` ์‹คํ–‰ ํ›„ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์ด ์„ค์น˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”: > `node -e "require('better-sqlite3')"`. `MODULE_NOT_FOUND` ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด > `npm approve-scripts better-sqlite3 && npm install`์„ ์‹คํ–‰ํ•˜์„ธ์š”. ์ž์„ธํ•œ ๋‚ด์šฉ์€ > [๋ฌธ์ œ ํ•ด๊ฒฐ](docs/guides/TROUBLESHOOTING.md#npm-v11-better-sqlite3-not-installed-cannot-find-module)์„ ์ฐธ์กฐํ•˜์„ธ์š”. - **Git** ### ๋ณต์ œ ๋ฐ ์„ค์น˜ ```bash git clone https://github.com/diegosouzapw/OmniRoute.git cd OmniRoute npm install ``` ### ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ```bash # ํ…œํ”Œ๋ฆฟ์—์„œ .env ์ƒ์„ฑ cp .env.example .env # ํ•„์ˆ˜ ์‹œํฌ๋ฆฟ ์ƒ์„ฑ echo "JWT_SECRET=$(openssl rand -base64 48)" >> .env echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env ``` ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ์ฃผ์š” ๋ณ€์ˆ˜: | ๋ณ€์ˆ˜ | ๊ฐœ๋ฐœ ๊ธฐ๋ณธ๊ฐ’ | ์„ค๋ช… | | ---------------------- | ------------------------ | -------------------- | | `PORT` | `20128` | ์„œ๋ฒ„ ํฌํŠธ | | `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | ํ”„๋ŸฐํŠธ์—”๋“œ ๊ธฐ๋ณธ URL | | `JWT_SECRET` | (์œ„์—์„œ ์ƒ์„ฑ) | JWT ์„œ๋ช… ์‹œํฌ๋ฆฟ | | `INITIAL_PASSWORD` | `CHANGEME` | ์ตœ์ดˆ ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ | | `APP_LOG_LEVEL` | `info` | ๋กœ๊ทธ ์ƒ์„ธ ์ˆ˜์ค€ | ### ๋Œ€์‹œ๋ณด๋“œ ์„ค์ • ๋Œ€์‹œ๋ณด๋“œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด์„œ๋„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ UI ํ† ๊ธ€์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: | ์„ค์ • ์œ„์น˜ | ํ† ๊ธ€ | ์„ค๋ช… | | ----------- | ------------------ | --------------------------- | | ์„ค์ • โ†’ ๊ณ ๊ธ‰ | ๋””๋ฒ„๊ทธ ๋ชจ๋“œ | ๋””๋ฒ„๊ทธ ์š”์ฒญ ๋กœ๊ทธ ํ™œ์„ฑํ™”(UI) | | ์„ค์ • โ†’ ์ผ๋ฐ˜ | ์‚ฌ์ด๋“œ๋ฐ” ํ‘œ์‹œ ์—ฌ๋ถ€ | ์‚ฌ์ด๋“œ๋ฐ” ์„น์…˜ ํ‘œ์‹œ/์ˆจ๊ธฐ๊ธฐ | ์ด ์„ค์ •์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜๋ฉฐ ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ์„ค์ •๋œ ๊ฒฝ์šฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ธฐ๋ณธ๊ฐ’๋ณด๋‹ค ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. ### ๋กœ์ปฌ์—์„œ ์‹คํ–‰ ```bash # ๊ฐœ๋ฐœ ๋ชจ๋“œ(ํ•ซ ๋ฆฌ๋กœ๋“œ) npm run dev # ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ npm run build # next build โ†’ .build/next/ ์‹คํ–‰ ํ›„ assembleStandalone โ†’ dist/ npm run start # ๊ธฐ์—ฌ์ž ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์œ„ํ•œ ๋น ๋ฅธ ๋ฐฑ์—”๋“œ/API ์ „์šฉ ์ปดํŒŒ์ผ npm run build:contributor # ๋ฆด๋ฆฌ์Šค ๋นŒ๋“œ(ํด๋ฆฐ ์žฌ๋นŒ๋“œ + HEAD ์„ผํ‹ฐ๋„ โ€” ๋ฐฐํฌ์— ํ•„์ˆ˜) npm run build:release # rm -rf .build dist && build + dist/BUILD_SHA ๊ธฐ๋ก # ์ผ๋ฐ˜์ ์ธ ํฌํŠธ ๊ตฌ์„ฑ PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev ``` ๊ธฐ์—ฌ์ž ๋นŒ๋“œ๋Š” ์ปดํŒŒ์ผ ์ „์šฉ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋…๋ฆฝ ์‹คํ–‰ํ˜• ๋ฐฐํฌํŒ์„ ๊ตฌ์„ฑํ•˜๊ฑฐ๋‚˜ ์„ ํƒ์  ๋„ค์ดํ‹ฐ๋ธŒ ํŒจํ‚ค์ง• ์ž์‚ฐ์„ ๋นŒ๋“œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ ๊ฐ€๋Šฅํ•œ ๋ฒˆ๋“ค์„ ๊ฒ€์ฆํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜ ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ### ๋นŒ๋“œ ์ถœ๋ ฅ ๊ตฌ์กฐ | ๋””๋ ‰ํ„ฐ๋ฆฌ | ๋‚ด์šฉ | ์ถ”์  ์—ฌ๋ถ€ | | --------- | --------------------------------------------------------------------- | --------- | | `src/` | ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์†Œ์Šค(TypeScript / TSX) | ์˜ˆ | | `.build/` | ์ค‘๊ฐ„ ์‚ฐ์ถœ๋ฌผ โ€” `next build` ์ถœ๋ ฅ(gitignore๋จ, `distDir = .build/next`) | ์•„๋‹ˆ์š” | | `dist/` | ๋ฐฐํฌ ๊ฐ€๋Šฅํ•œ ๋ฒˆ๋“ค โ€” `assembleStandalone`๋กœ ๊ตฌ์„ฑ๋จ(gitignore๋จ) | ์•„๋‹ˆ์š” | ๋นŒ๋“œ ํŒŒ์ดํ”„๋ผ์ธ์€ ๋‹จ์ผ ํŒจ์Šค๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค: ``` npm run build โ””โ”€ next build โ†’ .build/next/standalone (Next.js ์ถœ๋ ฅ) โ””โ”€ assembleStandalone() (๋…๋ฆฝ ์‹คํ–‰ํ˜• ํŒŒ์ผ + ์ •์  ํŒŒ์ผ + public + ๋„ค์ดํ‹ฐ๋ธŒ ์ž์‚ฐ ๋ณต์‚ฌ) โ””โ”€ ์ถœ๋ ฅ: dist/ (server.js, .next/static/, public/, node_modules/) ``` `npm run build:release`๋Š” ๋จผ์ € ๋‘ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์ •๋ฆฌํ•˜๊ณ , ๋ฐฐํฌ ๋ฌด๊ฒฐ์„ฑ ์„ผํ‹ฐ๋„๋กœ `dist/BUILD_SHA`(= `git rev-parse --short HEAD`)๋ฅผ ์ถ”๊ฐ€๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. `npm run build:contributor`๋Š” ๋ฐฑ์—”๋“œ ์ „์šฉ ๋นŒ๋“œ ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œํ•˜๋Š” ๋™์•ˆ ๋Œ€์‹œ๋ณด๋“œ UI ํŒŒ์ผ์„ ์ž„์‹œ ์Šคํ…์œผ๋กœ ๋Œ€์ฒดํ•˜๊ณ  API ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์œ ์ง€ํ•˜๋ฉฐ ๋นŒ๋“œ ํ›„ ์›๋ณธ ํŒŒ์ผ์„ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹œ๋ณด๋“œ UI์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด๋‚˜ ์ „์ฒด ๋ฆด๋ฆฌ์Šค ๊ฒ€์ฆ์—๋Š” `npm run build`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ๊ธฐ์—ฌ์ž ํ”„๋กœํ•„์€ ๋ฆด๋ฆฌ์Šค ๋นŒ๋“œ๋ฅผ ๋Œ€์ฒดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. > **VPS ๋ฐฐํฌ ์ฐธ๊ณ :** ์›๊ฒฉ ์ด๋ฏธ์ง€ ๋””๋ ‰ํ„ฐ๋ฆฌ `/usr/lib/node_modules/omniroute/app/`๋Š” > ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ ์Šคํ‚ฌ์€ `dist/`์˜ ๋‚ด์šฉ์„ ์ด ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ rsyncํ•ฉ๋‹ˆ๋‹ค. > ์ €์žฅ์†Œ ๋‚ด๋ถ€์˜ ๋นŒ๋“œ ์ถœ๋ ฅ ๊ฒฝ๋กœ๋งŒ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค(`app/` โ†’ `dist/`). ๊ธฐ๋ณธ URL: - **๋Œ€์‹œ๋ณด๋“œ**: `http://localhost:20128/dashboard` - **API**: `http://localhost:20128/v1` --- ## Git ์›Œํฌํ”Œ๋กœ > โš ๏ธ **์ ˆ๋Œ€๋กœ `main`์— ์ง์ ‘ ์ปค๋ฐ‹ํ•˜์ง€ ๋งˆ์„ธ์š”.** ํ•ญ์ƒ ๊ธฐ๋Šฅ ๋ธŒ๋žœ์น˜๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. > > **PR ๋ฒ ์ด์Šค:** `main`์ด ์•„๋‹ˆ๋ผ ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ `release/vX.Y.Z` ๋ธŒ๋žœ์น˜๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•˜์„ธ์š”. > ๋ธŒ๋žœ์น˜๋ณ„ ๋ฆด๋ฆฌ์Šค + ๋ฐฐํฌ ์‹œ ํƒœ๊ทธ ์ง€์ • ๋ชจ๋ธ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ > [`docs/ops/BRANCHING_MODEL.md`](docs/ops/BRANCHING_MODEL.md)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ```bash # ํ™œ์„ฑ ๋ฆด๋ฆฌ์Šค์˜ ์ตœ์‹  ์ง€์ ์—์„œ ๋ธŒ๋žœ์น˜ ์ƒ์„ฑ(์˜ˆ: release/v3.8.49) git fetch origin git checkout -b feat/your-feature-name origin/release/v3.8.49 # ... ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์ ์šฉ ... git commit -m "feat: ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์„ค๋ช…" git push -u origin feat/your-feature-name # base = release/v3.8.49๋กœ Pull Request ์—ด๊ธฐ ``` ### ๋ธŒ๋žœ์น˜ ๋ช…๋ช… ๊ทœ์น™ | ์ ‘๋‘์‚ฌ | ์šฉ๋„ | | ----------- | ---------------- | | `feat/` | ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ | | `fix/` | ๋ฒ„๊ทธ ์ˆ˜์ • | | `refactor/` | ์ฝ”๋“œ ๊ตฌ์กฐ ์žฌ๊ตฌ์„ฑ | | `docs/` | ๋ฌธ์„œ ๋ณ€๊ฒฝ | | `test/` | ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€/์ˆ˜์ • | | `chore/` | ๋„๊ตฌ, CI, ์ข…์†์„ฑ | ### ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ [Conventional Commits](https://www.conventionalcommits.org/)๋ฅผ ๋”ฐ๋ฅด์„ธ์š”. ``` feat: ๊ณต๊ธ‰์ž ํ˜ธ์ถœ์— ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ถ”๊ฐ€ fix: JWT ์‹œํฌ๋ฆฟ ๊ฒ€์ฆ์˜ ์—ฃ์ง€ ์ผ€์ด์Šค ํ•ด๊ฒฐ docs: PII ๋ณดํ˜ธ ๋‚ด์šฉ์„ SECURITY.md์— ์ถ”๊ฐ€ test: ๊ด€์ธก ๊ฐ€๋Šฅ์„ฑ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ refactor(db): ์†๋„ ์ œํ•œ ํ…Œ์ด๋ธ” ํ†ตํ•ฉ ``` ์Šค์ฝ”ํ”„(v3.8): `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`, `cloud-agent`, `guardrails`, `compression`, `auto-combo`, `resilience`, `providers`, `executors`, `translator`, `domain`, `authz`. --- ## ํ…Œ์ŠคํŠธ ์‹คํ–‰ ```bash # ๋ชจ๋“  ํ…Œ์ŠคํŠธ(unit + vitest + ecosystem + e2e) npm run test:all # ๋‹จ์ผ ํ…Œ์ŠคํŠธ ํŒŒ์ผ(Node.js ๋„ค์ดํ‹ฐ๋ธŒ ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ โ€” ๋Œ€๋ถ€๋ถ„์˜ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉ) node --import tsx/esm --test tests/unit/your-file.test.ts # ๋ณ€๊ฒฝ ์‚ฌํ•ญ์˜ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰(CI ๊ฒŒ์ดํŠธ์™€ ๋™์ผํ•œ TIA ์„ ํƒ๊ธฐ, #8084) npm run test:scoped # ๋งˆ์ง€๋ง‰ ์ปค๋ฐ‹(๋˜๋Š” ์ž‘์—… ํŠธ๋ฆฌ)์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ npm run test:scoped:staged # ์Šคํ…Œ์ด์ง•๋œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋งŒ โ€” pre-commit ์‹คํ–‰๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์Œ npm run test:scoped:full # ๋จผ์ € import-graph ๋งต์„ ๋‹ค์‹œ ๋นŒ๋“œ(ํŒŒ์ผ ์ถ”๊ฐ€/์ด๋™ ํ›„) # ์ข…๋ฃŒ ์ฝ”๋“œ 1 + "์ „์ฒด ์Šค์œ„ํŠธ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”"๋Š” ํ—ˆ๋ธŒ ํŒŒ์ผ(tsconfig, package.json, โ€ฆ) ๋˜๋Š” # ๋งคํ•‘๋˜์ง€ ์•Š์€ ์†Œ์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Œ์„ ์˜๋ฏธํ•จ โ€” ์„ ํƒ๊ธฐ๋Š” ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํŒจํ•˜๋ฉฐ ์ ˆ๋Œ€๋กœ ์กฐ์šฉํžˆ ๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š์Œ. # Vitest(MCP ์„œ๋ฒ„, autoCombo, ์บ์‹œ) npm run test:vitest # E2E ํ…Œ์ŠคํŠธ(Playwright ํ•„์š”) npm run test:e2e # ํ”„๋กœํ† ์ฝœ ํด๋ผ์ด์–ธํŠธ E2E(MCP ์ „์†ก, A2A) npm run test:protocols:e2e # ์ƒํƒœ๊ณ„ ํ˜ธํ™˜์„ฑ ํ…Œ์ŠคํŠธ npm run test:ecosystem # ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒŒ์ดํŠธ: ๊ตฌ๋ฌธ/๋ผ์ธ/ํ•จ์ˆ˜/๋ถ„๊ธฐ 60% npm run test:coverage npm run coverage:report # ๋ฆฐํŠธ + ํ˜•์‹ ๊ฒ€์‚ฌ npm run lint npm run check # ๊ฒŒ์ดํŠธ๊ฐ€ ์ ์šฉ๋œ ์‹ค์ œ ์—…์ŠคํŠธ๋ฆผ ์ฝค๋ณด ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ(VPS ์ ‘๊ทผ ๊ถŒํ•œ + ์‹ค์ œ ์ œ๊ณต์ž ํฌ๋ ˆ๋”ง ํ•„์š”) # ์‹ค์ œ ์ œ๊ณต์ž๋ฅผ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ ์•ฝ๊ฐ„์˜ ๋น„์šฉ์ด ๋ฐœ์ƒํ•จ. CI์—์„œ๋Š” ์ ˆ๋Œ€๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์Œ. ๊ฒŒ์ดํŠธ๊ฐ€ ์—†์œผ๋ฉด ๋ฌธ์ œ์—†์ด ๊ฑด๋„ˆ๋œ€. # ํ•„์š” ์‚ฌํ•ญ: ssh root@192.168.0.15 ์ ‘๊ทผ ๊ถŒํ•œ(VPS์—์„œ ์ฝ๊ธฐ ์ „์šฉ DB ์Šค๋ƒ…์ƒท์„ ๊ฐ€์ ธ์˜ด). RUN_COMBO_LIVE=1 npm run test:combo:live # Phase-3 VPS ๋ผ์ด๋ธŒ ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ โ€” ์ผ๋ฐ˜ Node ESM ์Šคํฌ๋ฆฝํŠธ๋กœ ๋ผ์ด๋ธŒ .15 ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•จ. # ํ•„์š” ์‚ฌํ•ญ: ssh root@192.168.0.15 ์ ‘๊ทผ ๊ถŒํ•œ(SSH sqlite๋ฅผ ํ†ตํ•ด ์ฝค๋ณด๋ฅผ ์ƒ์„ฑ/์‚ญ์ œํ•จ). # ์‹ค์ œ ์ œ๊ณต์ž๋ฅผ ํ˜ธ์ถœํ•จ(์†Œ์•ก์˜ ๋น„์šฉ ๋ฐœ์ƒ). __live_test__* ์ฝค๋ณด๋งŒ ์ƒ์„ฑ/์‚ญ์ œํ•จ. CI์—์„œ๋Š” ์ ˆ๋Œ€๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์Œ. # .15์—์„œ๋Š” REQUIRE_API_KEY=false์ด๋ฏ€๋กœ API ํ‚ค๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์ง€๋งŒ, ์„ค์ •๋œ ๊ฒฝ์šฐ COMBO_LIVE_BASE_URL / COMBO_LIVE_API_KEY๋ฅผ ๋”ฐ๋ฆ„. npm run test:combo:live:vps # HTTP ์‹œ๋‚˜๋ฆฌ์˜ค 7๊ฐœ(priority/round-robin/weighted/cost/fusion/auto + health) npm run test:combo:live:vps:failover # ์‹ค์ œ ์ œ๊ณต์ž ๊ฐ„ ์žฅ์•  ์กฐ์น˜ ์‹œ๋‚˜๋ฆฌ์˜ค ์ถ”๊ฐ€(์ด 8๊ฐœ) ``` ์ปค๋ฒ„๋ฆฌ์ง€ ์ฐธ๊ณ  ์‚ฌํ•ญ: - `npm run test:coverage`๋Š” ๊ธฐ๋ณธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ์˜ ์†Œ์Šค ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ธก์ •ํ•˜๊ณ , `tests/**`๋ฅผ ์ œ์™ธํ•˜๋ฉฐ, `open-sse/**`๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค - ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋Š” ๊ตฌ๋ฌธ/๋ผ์ธ/ํ•จ์ˆ˜/๋ถ„๊ธฐ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒŒ์ดํŠธ๋ฅผ **60%+**๋กœ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค - PR์—์„œ `src/`, `open-sse/`, `electron/` ๋˜๋Š” `bin/`์˜ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ ๋™์ผํ•œ PR์—์„œ ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค - `npm run coverage:report`๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ ์ปค๋ฒ„๋ฆฌ์ง€ ์‹คํ–‰์˜ ์ƒ์„ธํ•œ ํŒŒ์ผ๋ณ„ ๋ณด๊ณ ์„œ๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค - `npm run test:coverage:legacy`๋Š” ๊ณผ๊ฑฐ ๋น„๊ต๋ฅผ ์œ„ํ•ด ์ด์ „ ์ธก์ • ์ง€ํ‘œ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค - ๋‹จ๊ณ„๋ณ„ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฐœ์„  ๋กœ๋“œ๋งต์€ `docs/ops/COVERAGE_PLAN.md`๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š” ### ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ์š”๊ตฌ ์‚ฌํ•ญ PR์„ ์—ด๊ธฐ ์ „์— [๊ธฐ์—ฌ ๊ณจ๋“  ํŒจ์Šค](docs/ops/CONTRIBUTION_GOLDEN_PATH.md)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€๊ฒฝํ•œ ํ•ญ๋ชฉ์— ๋งž๋Š” ์ง‘์ค‘ ๋ฃจํ”„๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”. ์ „์ฒด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ(CI ์ƒค๋“œ 4๊ฐœ), Vitest, **60%+** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒŒ์ดํŠธ ๋ฐ ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ๋Š” CI๊ฐ€ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•ด๋„ PR ๊ฒ€์‚ฌ๊ฐ€ ์ด๋ฏธ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์—†์œผ๋ฉฐ, ์‚ฌ์–‘์ด ๋‚ฎ์€ ๋จธ์‹ ์—์„œ๋Š” ํ˜ธ์ŠคํŠธ์˜ ์ž์›์„ ์†Œ์ง„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(#8084): - ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋‹ค๋ฃจ๋Š” ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ์‹คํ–‰ํ•˜์„ธ์š”: `node --import tsx/esm --test tests/unit/.test.ts` - `npm run lint`๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š” - ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋™์ผํ•œ PR์— ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š” - ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€๋œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ PR ์„ค๋ช…์— ํฌํ•จํ•˜์„ธ์š” - CI์— ํ”„๋กœ์ ํŠธ ์‹œํฌ๋ฆฟ์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด PR์—์„œ SonarQube ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š” ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ƒํƒœ: ๋‹ค์Œ ํ•ญ๋ชฉ์„ ๋‹ค๋ฃจ๋Š” **๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํŒŒ์ผ 122๊ฐœ**: - ์ œ๊ณต์ž ๋ณ€ํ™˜๊ธฐ ๋ฐ ํ˜•์‹ ๋ณ€ํ™˜ - ์†๋„ ์ œํ•œ, ํšŒ๋กœ ์ฐจ๋‹จ๊ธฐ ๋ฐ ๋ณต์›๋ ฅ - ์‹œ๋งจํ‹ฑ ์บ์‹œ, ๋ฉฑ๋“ฑ์„ฑ, ์ง„ํ–‰ ์ƒํ™ฉ ์ถ”์  - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—… ๋ฐ ์Šคํ‚ค๋งˆ(DB ๋ชจ๋“ˆ 21๊ฐœ) - OAuth ํ๋ฆ„ ๋ฐ ์ธ์ฆ - API ์—”๋“œํฌ์ธํŠธ ๊ฒ€์ฆ(Zod v4) - MCP ์„œ๋ฒ„ ๋„๊ตฌ ๋ฐ ๋ฒ”์œ„ ์ ์šฉ - ๋ฉ”๋ชจ๋ฆฌ ๋ฐ Skills ์‹œ์Šคํ…œ --- ## ์ฝ”๋“œ ์Šคํƒ€์ผ - **ESLint** โ€” ์ปค๋ฐ‹ํ•˜๊ธฐ ์ „์— `npm run lint` ์‹คํ–‰ - **Prettier** โ€” ์ปค๋ฐ‹ ์‹œ `lint-staged`๋ฅผ ํ†ตํ•ด ์ž๋™ ํฌ๋งทํŒ…(๋“ค์—ฌ์“ฐ๊ธฐ 2์นธ, ์„ธ๋ฏธ์ฝœ๋ก , ํฐ๋”ฐ์˜ดํ‘œ, ์ค„ ๋„ˆ๋น„ 100์ž, es5 ํ›„ํ–‰ ์‰ผํ‘œ) - **TypeScript** โ€” ๋ชจ๋“  `src/` ์ฝ”๋“œ๋Š” `.ts`/`.tsx`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , `open-sse/`๋Š” `.ts`/`.js`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, TSDoc(`@param`, `@returns`, `@throws`)์œผ๋กœ ๋ฌธ์„œํ™” - **`eval()` ๊ธˆ์ง€** โ€” ESLint์—์„œ `no-eval`, `no-implied-eval`, `no-new-func` ๊ทœ์น™ ์ ์šฉ - **Zod ๊ฒ€์ฆ** โ€” ๋ชจ๋“  API ์ž…๋ ฅ ๊ฒ€์ฆ์— Zod v4 ์Šคํ‚ค๋งˆ ์‚ฌ์šฉ - **๋ช…๋ช… ๊ทœ์น™**: ํŒŒ์ผ = camelCase/kebab-case, ์ปดํฌ๋„ŒํŠธ = PascalCase, ์ƒ์ˆ˜ = UPPER_SNAKE ### ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ / ๋นˆ catch ๋ธ”๋ก `catch`๋ฅผ ์„ค๋ช… ์—†์ด ๋‘์ง€ ๋งˆ์„ธ์š”. ๋‹ค์Œ ๋‘ ๋ฒ”์ฃผ ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”(โ€œSSE ์ŠคํŠธ๋ฆผ์—์„œ ์˜ค๋ฅ˜๋ฅผ ์ ˆ๋Œ€ ์กฐ์šฉํžˆ ๋ฌด์‹œํ•˜์ง€ ์•Š๋Š”๋‹คโ€๋Š” ์—„๊ฒฉํ•œ ๊ทœ์น™์„ ๊ตฌ์ฒดํ™”ํ•ฉ๋‹ˆ๋‹ค). - **์˜๋„์ ์ธ ๊ฒฝ์šฐ(์ž์ฒด์ ์ธ ์ตœ์„ ํ˜• ์ •๋ฆฌ/ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ)** โ€” ์—ฌ๊ธฐ์„œ์˜ ์‹คํŒจ๋Š” ์˜ˆ์ƒ๋œ ๊ฒƒ์ด๋ฉฐ ๋ฌดํ•ดํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊น… ์—†์ด ํ•œ ์ค„์˜ ๊ทผ๊ฑฐ ์„ค๋ช… ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”(๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•œ ๋กœ๊น…์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์žก์Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๊ทœ์น™์ž…๋‹ˆ๋‹ค). ```ts } catch {} // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ ํ›„ ์ด๋ฏธ ๋‹ซํžŒ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋‹ซ๋Š” ๊ฒƒ์€ ์˜ˆ์ƒ๋œ ๋™์ž‘์ž…๋‹ˆ๋‹ค ``` - **๋กœ๊ทธ๋ฅผ ๋‚จ๊ฒจ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ(์™ธ๋ถ€/ํ˜ธ์ถœ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ฝ”๋“œ์ด๊ฑฐ๋‚˜, ์˜ค๋ฅ˜ ๋ฌด์‹œ๊ฐ€ ์ œ์–ด ํ๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ)** โ€” `catch`๋ฅผ ์œ ์ง€ํ•˜๋˜(์ŠคํŠธ๋ฆผ์„ ์ค‘๋‹จ์‹œํ‚ค์ง€ ์•Š๋„๋ก ํ•จ), ์‹คํŒจ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งฅ๋ฝ์ด ํฌํ•จ๋œ `console.debug`/`warn` ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜์„ธ์š”. ```ts } catch (e) { console.debug("[STREAM] onFailure ์ฝœ๋ฐฑ ์˜ค๋ฅ˜:", e); } ``` ์ ์šฉ ์˜ˆ์‹œ๋Š” `open-sse/utils/stream.ts` ๋ฐ `open-sse/utils/streamHandler.ts`๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. --- ## Project Structure ``` src/ # TypeScript (.ts / .tsx) โ”œโ”€โ”€ app/ # Next.js 16 App Router โ”‚ โ”œโ”€โ”€ (dashboard)/ # Dashboard pages (23 sections) โ”‚ โ”œโ”€โ”€ api/ # API routes (51 directories) โ”‚ โ””โ”€โ”€ login/ # Auth pages (.tsx) โ”œโ”€โ”€ domain/ # Policy engine (policyEngine, comboResolver, costRules, etc.) โ”œโ”€โ”€ lib/ # Core business logic (.ts) โ”‚ โ”œโ”€โ”€ a2a/ # Agent-to-Agent v0.3 protocol server โ”‚ โ”œโ”€โ”€ acp/ # Agent Communication Protocol registry โ”‚ โ”œโ”€โ”€ compliance/ # Compliance policy engine โ”‚ โ”œโ”€โ”€ db/ # SQLite database layer (110 top-level modules + 130 migrations) โ”‚ โ”œโ”€โ”€ memory/ # Persistent conversational memory โ”‚ โ”œโ”€โ”€ oauth/ # OAuth providers, services, and utilities โ”‚ โ”œโ”€โ”€ skills/ # Extensible skill framework โ”‚ โ”œโ”€โ”€ usage/ # Usage tracking and cost calculation โ”‚ โ””โ”€โ”€ localDb.ts # Re-export layer only โ€” never add logic here โ”œโ”€โ”€ middleware/ # Request middleware (promptInjectionGuard) โ”œโ”€โ”€ mitm/ # MITM proxy (cert, DNS, target routing) โ”œโ”€โ”€ shared/ โ”‚ โ”œโ”€โ”€ components/ # React components (.tsx) โ”‚ โ”œโ”€โ”€ constants/ # Provider definitions (329), MCP scopes, routing strategies โ”‚ โ”œโ”€โ”€ utils/ # Circuit breaker, sanitizer, auth helpers โ”‚ โ””โ”€โ”€ validation/ # Zod v4 schemas โ””โ”€โ”€ sse/ # SSE proxy pipeline open-sse/ # @omniroute/open-sse workspace โ”œโ”€โ”€ executors/ # 89 executor implementation modules โ”œโ”€โ”€ handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.) โ”œโ”€โ”€ mcp-server/ # MCP server (107 tools, 3 transports, 32 scopes) โ”œโ”€โ”€ services/ # 178 top-level services (combo, autoCombo, rateLimitManager, etc.) โ”œโ”€โ”€ translator/ # Format translators (OpenAI โ†” Claude โ†” Gemini โ†” Responses โ†” Ollama) โ”œโ”€โ”€ transformer/ # Responses API transformer โ””โ”€โ”€ utils/ # 22 utility modules (stream, TLS, proxy, logging) electron/ # Electron desktop app (cross-platform) tests/ โ”œโ”€โ”€ unit/ # Node.js test runner (122 test files) โ”œโ”€โ”€ integration/ # Integration tests โ”œโ”€โ”€ e2e/ # Playwright tests โ”œโ”€โ”€ security/ # Security tests โ”œโ”€โ”€ translator/ # Translator-specific tests โ””โ”€โ”€ load/ # Load tests docs/ # Documentation โ”œโ”€โ”€ ARCHITECTURE.md # System architecture โ”œโ”€โ”€ API_REFERENCE.md # All endpoints โ”œโ”€โ”€ USER_GUIDE.md # Provider setup, CLI integration โ”œโ”€โ”€ TROUBLESHOOTING.md # Common issues โ”œโ”€โ”€ MCP-SERVER.md # MCP server (107 tools) โ”œโ”€โ”€ A2A-SERVER.md # A2A agent protocol โ”œโ”€โ”€ AUTO-COMBO.md # Auto-combo engine โ”œโ”€โ”€ CLI-TOOLS.md # CLI tools integration โ”œโ”€โ”€ COVERAGE_PLAN.md # Test coverage improvement plan โ”œโ”€โ”€ openapi.yaml # OpenAPI specification โ””โ”€โ”€ adr/ # Architecture Decision Records ``` --- ## ์ƒˆ Provider ์ถ”๊ฐ€ ### 1๋‹จ๊ณ„: Provider ์ƒ์ˆ˜ ๋“ฑ๋ก `src/shared/constants/providers.ts`์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ๋กœ๋“œ ์‹œ Zod๋กœ ๊ฒ€์ฆ๋ฉ๋‹ˆ๋‹ค. ### 2๋‹จ๊ณ„: Executor ์ถ”๊ฐ€(์‚ฌ์šฉ์ž ์ง€์ • ๋กœ์ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ) ๊ธฐ๋ณธ executor๋ฅผ ํ™•์žฅํ•˜์—ฌ `open-sse/executors/your-provider.ts`์— executor๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ### 3๋‹จ๊ณ„: Translator ์ถ”๊ฐ€(OpenAI ํ˜•์‹์ด ์•„๋‹Œ ๊ฒฝ์šฐ) `open-sse/translator/`์— ์š”์ฒญ/์‘๋‹ต translator๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ### 4๋‹จ๊ณ„: OAuth ๊ตฌ์„ฑ ์ถ”๊ฐ€(OAuth ๊ธฐ๋ฐ˜์ธ ๊ฒฝ์šฐ) `src/lib/oauth/constants/oauth.ts`์— OAuth ์ž๊ฒฉ ์ฆ๋ช…์„ ์ถ”๊ฐ€ํ•˜๊ณ  `src/lib/oauth/services/`์— ์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์—…์ŠคํŠธ๋ฆผ provider๊ฐ€ ๊ณต๊ฐœ CLI/๋ธŒ๋ผ์šฐ์ € ๋ฒˆ๋“ค ๋‚ด์— ๊ณต๊ฐœ OAuth client_id/secret ๋˜๋Š” Firebase Web API ํ‚ค๋ฅผ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ, ์ด๋ฅผ ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด๋กœ **์‚ฝ์ž…ํ•˜์ง€ ๋งˆ์„ธ์š”**. `open-sse/utils/publicCreds.ts`์˜ `resolvePublicCred()`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  `EMBEDDED_DEFAULTS`์— ๋งˆ์Šคํ‚น๋œ ๋ฐ”์ดํŠธ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์•ผ ํ•˜๋Š” ์ „์ฒด ์›Œํฌํ”Œ๋กœ๋Š” [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)์— ๋ฌธ์„œํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. handler/executor ๋‚ด๋ถ€์—์„œ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ๋˜๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” `open-sse/utils/error.ts`์˜ `buildErrorBody()` / `sanitizeErrorMessage()`๋ฅผ ๋ฐ˜๋“œ์‹œ ๊ฑฐ์ณ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์›์‹œ `err.stack` ๋˜๋Š” `err.message`๋ฅผ Response ๋ณธ๋ฌธ์— ์ ˆ๋Œ€๋กœ ๋„ฃ์ง€ ๋งˆ์„ธ์š”. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ### 5๋‹จ๊ณ„: ๋ชจ๋ธ ๋“ฑ๋ก `open-sse/config/providerRegistry.ts`์— ๋ชจ๋ธ ์ •์˜๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ### 6๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ ์ตœ์†Œํ•œ ๋‹ค์Œ ํ•ญ๋ชฉ์„ ๋‹ค๋ฃจ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ `tests/unit/`์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. - Provider ๋“ฑ๋ก - ์š”์ฒญ/์‘๋‹ต ๋ณ€ํ™˜ - ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ --- ## ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ - [ ] ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (`npm test`) - [ ] ๋ฆฐํŠธ ๊ฒ€์‚ฌ ํ†ต๊ณผ (`npm run lint`) - [ ] ๋นŒ๋“œ ์„ฑ๊ณต (`npm run build`) - [ ] ์ƒˆ๋กœ์šด ๊ณต๊ฐœ ํ•จ์ˆ˜ ๋ฐ ์ธํ„ฐํŽ˜์ด์Šค์— TypeScript ํƒ€์ž… ์ถ”๊ฐ€ - [ ] ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์‹œํฌ๋ฆฟ ๋˜๋Š” ๋Œ€์ฒด ๊ฐ’ ์—†์Œ - [ ] ๊ณต๊ฐœ ์—…์ŠคํŠธ๋ฆผ ์ž๊ฒฉ ์ฆ๋ช…์€ ๋ฆฌํ„ฐ๋Ÿด์ด ์•„๋‹Œ `resolvePublicCred()`๋ฅผ ํ†ตํ•ด ํฌํ•จ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ์ฐธ์กฐ) - [ ] ์˜ค๋ฅ˜ ์‘๋‹ต์€ `buildErrorBody()` / `sanitizeErrorMessage()`๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ โ€” ์‘๋‹ต ๋ณธ๋ฌธ์— ์›์‹œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ํฌํ•จ ๊ธˆ์ง€ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ์ฐธ์กฐ) - [ ] ์…ธ ๋ช…๋ น์–ด(`exec` / `spawn`)๋Š” ๋ฌธ์ž์—ด ๋ณด๊ฐ„์ด ์•„๋‹Œ `env`๋ฅผ ํ†ตํ•ด ๋Ÿฐํƒ€์ž„ ๊ฐ’์„ ์ „๋‹ฌ - [ ] ๋ชจ๋“  ์ž…๋ ฅ์„ Zod ์Šคํ‚ค๋งˆ๋กœ ๊ฒ€์ฆ - [ ] ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•ด `changelog.d/{features|fixes|maintenance}/-.md` ์•„๋ž˜์— ๋ณ€๊ฒฝ ๋กœ๊ทธ **์กฐ๊ฐ** ์ถ”๊ฐ€ ([`changelog.d/README.md`](./changelog.d/README.md) ์ฐธ์กฐ) โ€” `CHANGELOG.md`๋ฅผ ์ง์ ‘ ํŽธ์ง‘ํ•˜์ง€ **๋ง ๊ฒƒ**. ์กฐ๊ฐ์€ ๋ฆด๋ฆฌ์Šค ์‹œ์ ์— ํ†ตํ•ฉ๋˜๋ฉฐ PR ๊ฐ„ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ - [ ] ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ(ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ์šฐ) - [ ] ์ƒˆ๋กœ์šด CodeQL / Secret-Scanning ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ๊ฐ ๊ฒฝ๊ณ ๋ฅผ ๊ด€๋ จ `docs/security/` ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ธฐ์ˆ ์  ๊ทผ๊ฑฐ์™€ ํ•จ๊ป˜ ํ•ด์ œ - [ ] ์ž์‹ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ผ์šฐํŠธ(`/api/mcp/`, `/api/cli-tools/runtime/`)๋ฅผ `src/server/authz/routeGuard.ts`์—์„œ `isLocalOnlyPath()`๋กœ ๋ถ„๋ฅ˜ โ€” [ํ•˜๋“œ ๊ทœ์น™ #15](docs/security/ROUTE_GUARD_TIERS.md) ์ฐธ์กฐ - [ ] ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€์— `Co-Authored-By` ํŠธ๋ ˆ์ผ๋Ÿฌ ํฌํ•จ ๊ธˆ์ง€ โ€” ์ปค๋ฐ‹์€ ์ €์žฅ์†Œ ์†Œ์œ ์ž์˜ Git ID๋กœ๋งŒ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•จ(ํ•˜๋“œ ๊ทœ์น™ #16) --- ## Releasing Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions. --- ## ๋„์›€๋ง ๋ณด๊ธฐ - **์•„ํ‚คํ…์ฒ˜**: [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) ์ฐธ์กฐ - **API ์ฐธ์กฐ ๋ฌธ์„œ**: [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) ์ฐธ์กฐ - **๋ณด์•ˆ ๋ฌธ์„œ**: [`docs/security/CLI_TOKEN.md`](docs/security/CLI_TOKEN.md), [`docs/security/ROUTE_GUARD_TIERS.md`](docs/security/ROUTE_GUARD_TIERS.md), [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md), [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) - **์šด์˜ ๋ฌธ์„œ**: [`docs/ops/SQLITE_RUNTIME.md`](docs/ops/SQLITE_RUNTIME.md) - **์ด์Šˆ**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) - **ADR**: ์•„ํ‚คํ…์ฒ˜ ๊ฒฐ์ • ๊ธฐ๋ก์€ `docs/adr/` ์ฐธ์กฐ