diff --git a/docs/PLAN-0.3.md b/docs/PLAN-0.3.md new file mode 100644 index 0000000..d033986 --- /dev/null +++ b/docs/PLAN-0.3.md @@ -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` 에 붙여 보고, 도구 호출 스트림이 어느 경로에 맞는지 확인한다.