Orca 기여 : 오픈소스 기여는 직접 만든 경험에서 시작됐다

댓글 2
댓글을 작성하려면 로그인이 필요합니다.
개발자라면 한 번쯤 오픈소스에 기여해 보고 싶다는 생각을 해봤을 것이다.
나도 그랬다.
평소 사용하는 오픈소스의 GitHub 저장소에 들어가 이슈를 구경하기도 하고, good first issue를 찾아보기도 했다.
그런데 막상 시작하려고 하면 어려웠다.
그래서 뭘 고쳐야 하지?
저장소에는 이미 수많은 코드가 있고, 처음 보는 구조와 이름이 가득했다.
오픈소스에 기여하려면 먼저 프로젝트 전체를 이해해야 할 것 같았다. 그러다 보니 실제 코드를 고치는 것보다 기여할 문제를 찾는 것 자체가 더 어려웠다.
그런데 생각하지 못한 곳에서 시작점이 생겼다.
내가 직접 에디터를 만들기 시작하면서였다.
Develog에서 글을 작성할 수 있는 에디터를 만들었다.
Markdown을 입력하고, 미리보기를 렌더링하고, 코드 블록에 syntax highlighting을 적용하고, 에디터 안에서 여러 입력을 처리했다.
사용자 입장에서 보면 그냥 하나의 글쓰기 화면이다.
하지만 직접 만들어보니 안에서는 훨씬 많은 일이 일어나고 있었다.
Markdown 문법 하나가 어떤 단계에서 처리되는지,
코드 블록의 언어 이름이 syntax highlighter와 어떻게 연결되는지,
같은 파일에서도 현재 커서가 어느 문맥에 있는지에 따라 왜 동작이 달라져야 하는지 생각하게 됐다.
에디터를 직접 만들기 전에는 이런 문제를 만나도 그냥 "조금 이상하네" 하고 지나갔을 것이다.
하지만 직접 만들어본 뒤에는 질문이 달라졌다.
이건 왜 이렇게 동작하지?
그리고 그 질문이 Orca를 사용하면서 실제 이슈와 PR로 이어졌다.
처음부터 Orca의 전체 구조를 공부한 것도 아니다.
오히려 반대였다.
내가 이미 경험해 본 영역에서 문제가 보였고, 그 문제를 따라가면서 필요한 코드만 읽기 시작했다.
내가 주로 했던 방식은 단순했다.
되는 경우를 찾는다.
안 되는 경우를 찾는다.
둘의 차이를 최대한 작게 만든다.
그리고 질문한다.
둘 사이에서 무엇이 달라졌을까?
이 방식으로 처음 발견한 것이 Markdown의 CJK 강조 문제였다.
Orca의 Markdown Preview를 사용하다 이상한 동작을 발견했다.
다음과 같은 Markdown이었다.
**"이런"**것은 강조되어야 합니다.
의도한 결과는 "이런" 부분만 굵게 표시되는 것이다.
하지만 Orca의 읽기 전용 Markdown Preview에서는 경우에 따라 **가 그대로 화면에 표시됐다.
**"이런"**것은 강조되어야 합니다.
영어 Markdown만 생각하면 사소하게 지나칠 수도 있는 문제였다.
하지만 한국어나 일본어, 중국어처럼 강조 표현 바로 뒤에 공백 없이 문자가 이어지는 환경에서는 충분히 만날 수 있는 형태였다.
실제로 비슷한 패턴이 있었다.
**(강조)**입니다.
**「強調」**です。
**“强调”**文本
이 문제를 GitHub Issue #20536으로 작성했다.
처음에는 단순히 화면에 표시되는 문제처럼 보였다.
그런데 ** 자체가 그대로 출력된다는 점을 보면 CSS보다는 그 이전 단계를 먼저 의심할 수 있었다.
조사해 보니 Orca의 Markdown Preview는 react-markdown과 remark-gfm을 사용하고 있었고, 기본 CommonMark delimiter 규칙에서는 이런 CJK 인접 강조 표현이 제대로 인식되지 않는 경우가 있었다.
그래서 CJK Markdown 파싱을 보완하는 remark-cjk-friendly의 parseOnly를 Preview 파서에 적용하는 방향으로 PR #20538을 만들었다.
단순히 패키지를 추가하는 것으로 끝내지는 않았다.
한국어뿐 아니라 일본어와 중국어 사례를 테스트하고, 기존 CommonMark와 GFM 문법에 영향을 주지 않는지도 함께 확인했다.
처음 오픈소스 PR을 생각했을 때는 이런 모습을 상상했다.
하지만 첫 PR부터 그렇게 흘러가지는 않았다.
내 PR이 열려 있는 동안 같은 문제를 해결하는 PR #21553이 다른 작업을 통해 먼저 main에 병합됐다.
이후 maintainer가 현재 main에 동일한 변경이 이미 들어와 있는 것을 확인하고 내 PR을 superseded 상태로 닫았다.
즉 내가 작성한 코드는 병합되지 않았다.
처음에는 Merge되지 않았으니 기여가 실패한 것처럼 생각할 수도 있었다.
그런데 조금 다르게 보이기 시작했다.
문제를 재현하고,
CJK 문맥에서 발생하는 조건을 좁히고,
해결 가능한 방향을 검증하고,
테스트 사례를 남기는 과정 역시 오픈소스에서 문제를 해결하는 과정이었다.
오픈소스 기여와 내 코드가 반드시 Merge되는 것은 같은 의미가 아니었다.
첫 번째 PR에서 그걸 배웠다.
sh는 되는데 bash는 왜 안 될까?두 번째 문제는 훨씬 단순했다.
Orca의 Markdown Source mode에서 다음 코드 블록을 작성했다.

그런데 syntax highlighting이 적용되지 않았다.
처음에는 syntax highlighting 자체에 문제가 있는 것처럼 보였다.
그런데 fence의 언어 이름만 sh로 바꾸면 정상적으로 동작했다.

정리하면 이랬다.
| Fence | 결과 |
|---|---|
sh | syntax highlighting 적용 |
bash | 평문으로 표시 |
여기서 중요한 것은 코드 내용은 전혀 바뀌지 않았다는 것이다.
달라진 것은 sh와 bash뿐이었다.
그렇다면 확인해야 할 범위가 크게 줄어든다.
관련 코드를 찾아보니 원인이 분명해졌다.
Orca의 Markdown Source mode는 Monaco Editor를 사용하고 있었고, Monaco의 shell language ID는 shell이었다.
sh는 이미 alias로 등록되어 있었지만 bash는 Markdown fence identifier를 통해 shell tokenizer로 연결되지 않고 있었다.
여기서 .bash 확장자가 등록되어 있다는 사실만으로는 충분하지 않았다.
Markdown 안에서는 파일 확장자를 보는 것이 아니라 fence에 적힌 bash라는 이름을 가지고 embedded language를 찾기 때문이다.
그래서 해결 방법은 생각보다 작았다.
기존 shell language에 bash를 alias로 추가했다.
shell
├── sh
├── shell
└── bash
그리고 기존 sh, shell 동작이 유지되는지와 반복 초기화에서도 문제가 없는지 테스트를 추가했다.
Issue #20584에서 시작한 이 변경은 PR #20592로 이어졌고, 2026년 9월 16일 Orca의 main에 병합됐다.

수정 자체는 크지 않았다.
그런데 오히려 이 경험이 꽤 중요했다.
예전에는 오픈소스에 기여하려면 새로운 기능을 만들거나 복잡한 문제를 해결해야 한다고 생각했다.
실제로는 그렇지 않았다.
사용자가 분명하게 겪는 문제이고,
재현할 수 있고,
원인을 설명할 수 있고,
안전하게 고칠 수 있다면,
몇 줄짜리 수정도 충분한 기여가 됐다.
그리고 이때부터 버그를 보면 가장 먼저 이런 질문을 하게 됐다.
정상 동작과 비정상 동작 사이의 가장 작은 차이는 무엇일까?
sh와 bash는 그 질문이 얼마나 강력한지 보여준 가장 단순한 사례였다.
https://github.com/stablyai/orca/releases/tag/v1.4.206

세 번째 문제는 앞의 bash 문제처럼 간단하게 끝나지 않았다.
Orca 에디터에서 JSX나 TSX 파일을 열고 Cmd + / 단축키로 주석을 만들 때 발생하는 문제였다.
JavaScript와 TypeScript에서는 다음 주석이 정상이다.
const message = 'hello'
// 일반 JavaScript / TypeScript 주석
그런데 JSX의 자식 영역에서는 다르다.
function App() {
return (
<div>
{/* JSX 주석 */}
</div>
)
}
문제는 Orca에서 JSX child를 선택한 뒤 Cmd + /를 누르면 다음과 같이 //가 들어간다는 것이었다.
const view = (
<section>
// <h1>Hello</h1>
</section>
)
JavaScript에서는 올바른 //가 JSX child에서는 올바른 주석 표현이 아니다.
이 문제를 Issue #20985로 작성했다.
처음에는 쉽게 생각할 수도 있다.
.jsx나.tsx파일이면{/* */}를 사용하면 되는 것 아닌가?
하지만 TSX 파일을 조금만 보면 바로 문제가 생긴다.
const title = 'Hello'
// 여기서는 // 가 맞다.
function App() {
return (
<main>
{/* 여기서는 JSX 주석이 맞다. */}
<h1>{title}</h1>
</main>
)
}
같은 .tsx 파일 안에서도 주석 방식이 달라져야 한다.
파일 확장자는 하나지만 내부에는 여러 문맥이 들어 있다.
결국 문제는 파일 확장자가 아니었다.
현재 선택 영역이 어느 문맥에 있는지를 판단해야 했다.
여기서부터 문제가 커졌다.
한 줄만 선택했을 때는?
여러 JSX child를 선택하면?
JavaScript 영역과 JSX 영역을 동시에 선택하면?
현재 문맥을 확실하게 판단할 수 없다면?
그리고 가장 중요하게는,
JSX 문제를 해결하면서 기존 JavaScript와 TypeScript의 정상적인 주석 동작까지 바꿔버리면 안 된다.
처음에는 작은 단축키 버그처럼 보였는데 실제로는 언어의 문맥을 어떻게 판단할 것인가라는 문제였다.
.tsx면 JSX 주석을 쓰게 만들지 않았다이 문제에서 특히 조심했던 부분이다.
가장 간단하게 구현하려면 이런 조건을 만들 수도 있다.
if (fileExtension === '.tsx') {
// JSX 주석 처리
}
하지만 앞에서 봤듯이 이 방식은 올바르지 않다.
TSX 파일 안에도 일반 TypeScript 영역이 있기 때문이다.
그래서 PR #20989에서는 Orca가 이미 사용하고 있던 vscode-textmate와 vscode-oniguruma 기반의 인프라를 활용해 JSX/TSX의 line context를 구분하는 방향으로 접근했다.
그리고 선택 영역을 크게 세 가지로 나눴다.
여기서 Script 영역은 기존 Monaco의 editor.action.commentLine에 그대로 맡긴다.
기존 JavaScript와 TypeScript 동작을 다시 구현하지 않는 것이다.
JSX child라고 안전하게 판단할 수 있을 때만 JSX 방식의 주석을 사용한다.
그리고 script와 JSX가 섞였거나 문맥을 확실히 판단할 수 없는 경우에는 억지로 수정하지 않는다.
이 부분이 개인적으로 가장 흥미로웠다.
처음에는 다음 질문이었다.
JSX에서
Cmd + /가 왜 안 되지?
그런데 코드를 따라가면서 질문이 바뀌었다.
기존 동작을 깨지 않으면서 어디까지 개입해야 하지?
새로운 동작을 추가하는 것보다 기존 동작을 얼마나 보존할 것인지 결정하는 일이 더 어려울 수 있다는 것을 경험했다.
2026년 9월 23일 기준 이 PR은 아직 열려 있다.
세 가지 문제를 정리하면 결과가 모두 다르다.
| 문제 | 결과 | 기억에 남은 점 |
|---|---|---|
| CJK Markdown 강조 | 내 PR은 다른 병합 PR에 의해 대체 | 내 코드가 Merge되지 않아도 문제를 재현하고 검증한 과정은 남는다 |
bash syntax highlighting | Merge | 작은 차이를 찾으면 원인을 빠르게 좁힐 수 있다 |
| JSX / TSX 주석 | Open | 단순한 UI 문제 뒤에 언어 문맥과 기존 동작 보존 문제가 숨어 있을 수 있다 |
처음에는 오픈소스 기여를 하나의 과정으로 생각했다.
이슈를 찾는다.
코드를 고친다.
PR을 올린다.
Merge된다.
실제로 해보니 그렇게 일정하지 않았다.
어떤 문제는 몇 줄의 변경으로 해결됐다.
어떤 문제는 코드보다 문제를 재현하는 것이 중요했다.
또 어떤 문제는 겉보기에는 작은 기능인데 생각해야 할 경계 조건이 계속 늘어났다.
그래서 지금은 Merge 여부만 가지고 기여 경험을 구분하지 않게 됐다.
이번 작업들은 Codex를 적극적으로 사용했다.
처음 보는 대형 코드베이스에서 특정 기능의 시작점을 찾는 것은 생각보다 시간이 많이 걸린다.
Codex는 이런 부분에서 특히 유용했다.
관련 코드가 어디에 있는지 찾고,
비슷한 구현이 있는지 검색하고,
테스트를 작성하고,
변경 범위를 검토하는 속도를 크게 높일 수 있었다.
CJK PR에서도 Codex를 이용해 Markdown 렌더링 경로를 조사했고, 구현과 regression test 초안을 만들고 검증 작업을 진행했다.
하지만 작업을 하면서 오히려 분명해진 부분도 있었다.
AI가 코드를 만들어주는 것과 그 코드가 프로젝트에 들어가도 되는지를 판단하는 것은 다른 문제였다.
예를 들어 bash 문제에서도 단순히 이렇게 말할 수 있다.
bash가 안 되니까sh로 바꾸자.
사용자 입장에서는 당장 동작할 수 있다.
하지만 오픈소스 프로젝트의 수정이라면 질문이 달라진다.
사용자의 Markdown을 바꾸는 것이 맞을까?
아니면 highlighter가
bash를 이해하도록 하는 것이 맞을까?
JSX 문제는 더 복잡했다.
.tsx에서는 전부 JSX 주석으로 바꾸면 되지 않을까?
구현 자체는 쉬울 수 있다.
하지만 기존 TypeScript 영역까지 깨뜨리게 된다.
결국 PR을 올리려면 내가 설명할 수 있어야 했다.
AI가 코드 작성 속도를 높여줄 수는 있지만, 변경에 대한 책임까지 대신 설명해 주지는 않는다.
오픈소스에 PR을 올리면서 이 차이를 더 크게 느꼈다.
good first issue가 아니어도 첫 기여는 시작할 수 있었다오픈소스 기여 방법을 찾아보면 good first issue를 많이 추천한다.
물론 좋은 방법이다.
관리자가 처음 참여하는 사람이 비교적 접근하기 좋은 문제를 표시해 둔 것이기 때문이다.
하지만 이번 경험에서는 반대 방향으로 시작했다.
이슈를 먼저 고른 것이 아니라, 내가 이미 알고 있는 경험에서 문제를 발견했다.
에디터를 만들어봤기 때문에 Markdown 렌더링 차이가 보였다.
코드 블록을 다뤄봤기 때문에 bash와 sh의 차이가 눈에 들어왔다.
React와 TSX를 사용해왔기 때문에 JSX child에 //가 들어가는 것이 이상하다고 바로 판단할 수 있었다.
그 경험을 정리해보면 첫 기여를 찾는 방법은 꼭 하나일 필요가 없다.
나에게는 두 번째 방법이 더 잘 맞았다.
세 번의 작업을 거치면서 생긴 습관도 있다.
이상한 동작을 발견했다고 바로 구현부터 시작하지 않는다.
먼저 정상 케이스와 실패 케이스를 만든다.
예를 들어 bash 문제라면:
sh는 된다.
bash는 안 된다.
CJK Markdown이라면:
일반적인 강조는 된다.
CJK 문자와 특정 punctuation이 붙으면 안 된다.
JSX라면:
JavaScript 영역에서는 //가 맞다.
JSX child에서는 //가 틀리다.
그리고 둘 사이에서 무엇이 달라지는지 본다.
이 과정을 반복하면 처음에는 큰 문제처럼 보이던 것이 작은 조건 하나로 줄어들기도 한다.
반대로 JSX 문제처럼 단순해 보였던 것이 실제로는 더 큰 문제라는 사실을 알게 되기도 한다.
둘 다 의미가 있었다.
문제를 작게 만드는 것 자체가 디버깅이었다.
예전에는 오픈소스 기여를 하려면 그 프로젝트를 잘 알아야 한다고 생각했다.
저장소 구조를 이해하고,
코드를 많이 읽고,
내부 구현을 충분히 알아야 비로소 고칠 문제를 찾을 수 있을 것 같았다.
실제로 해보니 순서가 반대였다.
문제를 먼저 발견했다.
그리고 그 문제를 이해하기 위해 필요한 코드를 읽었다.
나는 아래쪽 방식으로 시작할 수 있었다.
Develog에서 에디터를 만들지 않았다면 Orca의 Markdown 문제를 그냥 지나쳤을지도 모른다.
코드 블록을 직접 다뤄보지 않았다면 sh와 bash의 차이를 단순한 불편으로 생각했을 수도 있다.
React와 TSX를 사용하지 않았다면 JSX child에 들어간 //가 왜 문제인지 바로 눈에 들어오지 않았을 것이다.
직접 만든 경험은 내 프로젝트 하나에서 끝나지 않았다.
다른 제품에서 무엇이 이상한지를 판단하는 기준이 됐다.
그래서 이제 새로운 오픈소스를 볼 때,
내가 이 거대한 프로젝트에서 뭘 고칠 수 있을까?
라고 생각하기보다 먼저 이렇게 생각해보려고 한다.
내가 이미 만들어보고 고민했던 것 중에서, 여기서는 다르게 동작하는 것이 없을까?
내 첫 오픈소스 기여는 거기서 시작됐다.

- 하네스·루프·그래프는 서로를 대체하는 게 아니라 겹쳐 있는 층이다. - 나누기만 하고 공유할 기준을 못 박지 않으면 합칠 때 어긋난다 (직접 겪은 사례). - Bun의 Rust 재작성은 가능성을 보여줬지만 사람의 검토를 대신하지 못했다. - 한계는 사라진 게 아니라 토큰 비용과 검토 책임으로 옮겨갔다.

우리들의 게임 발매 이야기

안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.

- 하네스·루프·그래프는 서로를 대체하는 게 아니라 겹쳐 있는 층이다. - 나누기만 하고 공유할 기준을 못 박지 않으면 합칠 때 어긋난다 (직접 겪은 사례). - Bun의 Rust 재작성은 가능성을 보여줬지만 사람의 검토를 대신하지 못했다. - 한계는 사라진 게 아니라 토큰 비용과 검토 책임으로 옮겨갔다.


안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.
너무 므찌다.. ㄷㄷ 지금 쓰는 Orca 에 ㄷㄷ
지금 쓰는거라 더 관심있게 봐서 그런것 같습니다 !!😂