Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions docs/PLAN-0.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 0.3.0 계획서 — 그리고 0.4.0 프리뷰

작성일: 2026-08-05 · 상태: 초안 · 기준 브랜치: `develop` (`main` 대비 45커밋)

---

## 0. 지금 어디에 있나

`develop` 에 쌓인 45커밋은 **전부 "고침"** 이다. 미저장 편집 유실(외부 삭제·종료·프로젝트 전환·되돌리기), BOM·UTF-8 아닌 파일 파괴, 승인 물음이 실행을 매달아 두던 것, 깊은 폴더·점 폴더가 앱 어디에도 안 보이던 것, 워처가 목록을 말없이 자르던 것.

좋은 릴리스 노트가 되지만 **큰 업데이트로는 안 읽힌다.** 헤드라인이 될 새 능력이 없기 때문이다. 0.3.0 은 그것을 채운다.

0.2.0 이 "확장이 할 수 있다고 말한 것을 실제로 하게 만든" 판이었다면, 0.3.0 은 **고를 수 있게 만드는** 판이다 — 어떤 모델로 돌릴지, 무엇을 볼지, 확장이 어디까지 손댈 수 있는지.

---

## 1. 0.3.0 범위 — 세 갈래

### 트랙 A — 프로바이더 확장 (Gemini · 로컬 모델)

로드맵 Phase 3 의 절반. 지금 붙어 있는 것은 Claude · GPT · Grok · GLM 넷이고, 앞의 셋은 `OpenAICompatProvider` 하나를 설정만 바꿔 재사용한다(`src/ai/openaiCompat.ts:303~`). 그 구조 덕에 이 트랙은 대부분 **설정과 UI 문제**이지 프로토콜 문제가 아니다.

**A-1. Gemini.** Google 이 OpenAI 호환 엔드포인트를 내주므로 `OpenAICompatProvider` 설정 한 줄로 시작한다. 확인해야 할 것은 호환 계층이 **도구 호출 스트리밍을 어떤 모양으로 흘리는가** 다 — `openaiCompat.ts` 의 두 경로(Responses 계열 `response.output_item.*`, Chat Completions 계열 `tool_calls` 델타) 중 어디에 붙는지, 인자 JSON 이 조각으로 오는지. 붙지 않으면 그때 전용 어댑터를 판다.

**A-2. 로컬 모델 (Ollama · LM Studio · llama.cpp).** 셋 다 OpenAI 호환 서버라 프로토콜은 같다. 새로 필요한 것은 이쪽이다:

- **주소를 사용자가 정한다.** 지금 URL 은 프로바이더에 상수로 박혀 있다. `baseUrl` 을 설정으로 빼고, 로컬은 API 키를 요구하지 않아야 한다(`isConfigured()` 가 키 없음을 미설정으로 보면 로컬은 영원히 못 쓴다).
- **모델 목록을 서버에서 읽는다.** 로컬은 사용자가 무엇을 받아 뒀는지 우리가 알 수 없다. `/v1/models` 를 물어 고르게 한다. 못 물으면 직접 입력.
- **연결 확인.** 서버가 안 떠 있으면 "네트워크 오류" 가 아니라 "그 주소에 서버가 없습니다" 로 말한다.
- **할당량 조회는 끈다.** `quotaProbe` 는 벤더 헤더를 읽는 것이라 로컬에 의미가 없다. 조용히 0 을 보여주면 고장으로 읽힌다.
- **도구 호출을 못 하는 모델이 있다.** 작은 로컬 모델은 tool use 를 지원하지 않거나 흉내만 낸다. 처음 실행에서 도구 호출이 한 번도 안 나오면 **그 사실을 말한다** — 조용히 대화만 하는 에이전트는 고장으로 보인다.

**왜 이걸 먼저 두나:** 위험이 가장 낮고 사용자 가치가 가장 분명하다("내 모델로 돌린다"). 그리고 트랙 C 와 달리 이미 있는 추상(`AgentProvider`)이 실제로 그 목적으로 설계돼 있다.

### 트랙 B — 에디터 코어 시각 효과 (Phase 2 마무리)

README 가 **진행 중**이라고 적어 둔 항목이고, 이 제품의 한 줄 정의("AI가 코드를 고치는 과정을 실시간으로 멋지게 보여주는")가 정확히 여기 걸려 있다. 지금 있는 것은 `src/editor/editAnimator.ts` 의 타이핑 반영 하나다.

**착수 전에 할 일이 하나 있다.** 무엇이 이미 있고 무엇이 빠졌는지 코드로 훑어 표로 만든다 — 지금 이 문서에 후보를 나열하면 없는 것을 있다고 적게 된다. Phase 2 를 "끝냈다" 고 말하려면 **끝의 정의**부터 필요하다. 그 표가 이 트랙의 첫 산출물이다.

### 트랙 C — 확장 셰임의 남은 구멍

README 가 이름으로 적어 둔 것들이다. 그런데 **README 의 설명이 한 군데 틀렸다.** 이렇게 적혀 있다:

> Each of those throws rather than returning an empty value, so an extension that needs one fails loudly instead of quietly doing nothing.

`WorkspaceEdit` 는 정말 던진다(`vscodeShim.ts:146~148`). 그런데 데코레이션은 아니다:

```ts
createTextEditorDecorationType: () => ({ dispose() {}, key: "sz-deco" }), // vscodeShim.ts:557
setDecorations() { /* 데코레이션 타입이 no-op 이라 여기도 no-op */ }, // shimDoc.ts:125
```

**조용히 성공하는 답이다** — 0.2.0 전체가 없애려 했던 바로 그 모양이 하나 남아 있고, 문서는 그렇지 않다고 말한다. 이 트랙의 첫 커밋은 기능이 아니라 **README 를 사실과 맞추는 것**이어야 한다.

우선순위:

1. **`WorkspaceEdit` 의 파일 생성·삭제·이름변경.** 리팩터 확장의 기본기다. 세 IPC(`writeFile` · `deleteEntry` · `renameEntry`)가 이미 있고 각자 안전장치도 있으므로(휴지통 경유, 덮어쓰기 금지) 셰임에서 이어 주는 일에 가깝다. **미저장 버퍼와 되돌리기**를 어떻게 다룰지가 진짜 설계 지점이다 — 이번 판에서 반복해 확인한 자리다.
2. **데코레이션.** Monaco 의 데코레이션 API 로 실제로 그린다. 인라인 blame, 커버리지 표시, 오류 하이라이트가 전부 여기 걸려 있다.
3. **커스텀 에디터 · 디버그 어댑터 API.** 크다. 0.3 에 넣을지는 A·B 가 끝난 뒤 남은 여력으로 정한다. 넣지 않으면 **던지게** 두고 README 에 그대로 적는다.

---

## 2. 잘라내는 선

세 트랙을 다 넣으면 0.3 이 안 끝난다. 순서와 기준을 미리 정해 둔다.

- **A 는 반드시 들어간다.** 헤드라인이다.
- **C-1(WorkspaceEdit 파일 조작)과 C 의 README 정정도 반드시 들어간다.** 후자는 하루짜리이고, 문서가 거짓말하는 상태로 릴리스하지 않는다.
- **B 는 조사 표까지가 최소선.** 표를 보고 나서, 한 판에 들어갈 만한 항목만 고른다.
- **C-3 은 선택.** 마감에 밀리면 다음으로 넘긴다 — 던지고 있으므로 조용히 고장 나지 않는다.

마일스톤은 기존 `v0.3 (2026-09-30)`.

---

## 3. 0.4.0 프리뷰 — 코드베이스 인덱싱

0.3 에는 넣지 않는다. 다만 **왜 이것이 다음인지**는 지금 적어 둔다. 근거가 이번 판에서 나왔기 때문이다.

### 문제

에이전트가 저장소를 보는 눈은 `list_files` 와 `search_files` 둘뿐이고, 둘 다 **매번 디스크를 훑어서** 답한다. 그 구조에서 나온 결함을 이번 판에만 세 건 고쳤다:

| 무엇 | 증상 |
|---|---|
| 깊이 상한 8 | 8단계보다 깊은 파일이 트리·검색·치환 **어디에도** 없었다 |
| 점 폴더 일괄 제외 | `.vscode/`·`.claude/` 안이 통째로 안 보였다 |
| 잘렸다고 말 안 함 | 상한에 걸린 목록을 "전부" 로 내밀어, 있는 파일을 "없다" 로 답했다 |

셋 다 막았지만 **근본은 그대로다.** 매번 훑는 구조에서는 상한이 필요하고, 상한이 있으면 "안 찾아본 것" 과 "없는 것" 이 계속 같은 답으로 나온다. 그리고 이름으로만 찾으므로 "인증을 어디서 하지" 같은 질문에는 애초에 답할 수 없다.

### 방향 (0.4 에서 결정할 것)

- **심볼 인덱스가 먼저다.** 파일·심볼·참조 관계를 미리 만들어 두면 "정의가 어디" · "누가 부르나" 를 훑지 않고 답한다. TypeScript 는 이미 언어 서비스가 떠 있어(`src/editor/lspClient.ts`) 그 산출물을 재료로 쓸 수 있는지부터 본다.
- **의미 검색은 그다음.** 임베딩은 모델·저장소·비용·프라이버시(로컬 임베딩이냐)가 전부 딸려 온다. 심볼 인덱스만으로 어디까지 되는지 본 뒤에 판단한다.
- **갱신이 진짜 문제다.** 인덱스는 낡는 순간 거짓말을 시작하고, 그건 지금 상태보다 나쁘다. 이번 판에서 확인했듯 **워처는 이름을 다 주지 못한다**(OS 가 흘린다). 그래서 "워처 알림으로 증분 갱신" 은 그 자체로 못 미덥고, 트리 비교·해시·주기 재확인 중 무엇을 섞을지가 설계의 중심이 된다.
- **낡았을 때 그렇다고 말해야 한다.** 이 판 내내 지킨 규칙이 인덱스에도 그대로 적용된다 — 모르는 것을 아는 척하지 않는다.

### 프리뷰의 뜻

0.4.0 은 **프리뷰**로 낸다. 인덱싱은 위의 갱신 문제 때문에 한 번에 옳게 만들기 어렵고, 틀린 인덱스는 조용히 잘못된 답을 만든다. 기본은 꺼 두고, 켠 사람에게는 인덱스가 언제 만들어졌고 무엇을 못 봤는지 보이게 한다.

---

## 4. 다음 한 걸음

1. 트랙 C 의 README 정정 — 데코레이션이 던지지 않고 조용히 no-op 이라는 사실을 문서에 반영한다.
2. 트랙 B 의 조사 표 — Phase 2 에 무엇이 있고 무엇이 빠졌는지.
3. 트랙 A-1 착수 — Gemini 를 `OpenAICompatProvider` 에 붙여 보고, 도구 호출 스트림이 어느 경로에 맞는지 확인한다.
Loading