VS Code용 Cline: TypeScript 프로젝트에 2주 사용했더니 이렇게 살아남았다

발행: (2026년 6월 7일 PM 09:02 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

Cline in VS Code: I used it two weeks on a TypeScript project and this survived
VS Code에서 Cline: TypeScript 프로젝트에 2주 동안 사용했으며 이것이 살아남았다

2005년, 인터넷 카페가 밤 11시에 문을 닫고 사람들로 가득 찼을 때는 문서를 읽을 시간이 없었다. 문제를 진단하고, 명령을 실행하고, 결과를 확인하고, 수정해야 했다. 이 경험은 나에게 도구가 실제로 무언가를 수행하기 전에 정확히 무엇을 할지 보여줄 때에 대한 깊은 존경경고 없이 행동하는 모든 것에 대한 깊은 의심을 심어주었다.

2024년에 자율 코딩 에이전트를 평가하기 시작했을 때, 같은 본능이 속도 벤치마크보다 먼저 권한 모델을 살펴보게 만들었다. Cline은 내가 실제 명령을 하나도 쓰기 전에 제한을 설정하는 데 한 시간 넘게 머물렀던 최초의 도구였다.

내가 먼저 말하고 싶은 핵심은 다음과 같다: 자율 코딩 에이전트는 모두 똑같지 않으며, Cline은 다른 도구보다 제어하기 쉬운 권한 모델을 가지고 있다—하지만 중요한 것은 도구 자체가 아니라 그 제한을 어떻게 설정하느냐이다. 기본 설정으로 Cline을 설치하고 복잡한 모듈을 리팩터링하도록 하면, 30분 정도 제한을 정의하고 무엇을 건드릴 수 있는지 명확히 지정했을 때와는 전혀 다른 경험을 하게 된다.

아래 내용은 실제 TypeScript 프로젝트에서 2주 동안 활발히 사용한 결과를 바탕으로 한 분석이며, 위임된 작업, 발생한 실수, 그리고 설정 결정들을 문서화한다. 벤치마크가 아니라 장인의 판단을 통해 얻은 결론이다.

Cline은 VS Code Marketplace에서 오픈 소스 확장으로 제공된다. 공식 설명은 Cline을 파일을 읽고, 터미널 명령을 실행하고, 브라우저를 탐색하고, 코드를 생성·편집할 수 있는 자율 에이전트라고 소개한다—모두 VS Code 안에서 이루어진다.

페이지에 명시된 내용

  • Cline은 기본적으로 승인 루프를 통해 동작한다. 파일 쓰기, 터미널 명령 실행 등 파괴적일 수 있는 행동마다 진행 전에 확인을 요구한다.
  • 특정 카테고리에 대해 “자동 승인(auto‑approve)” 모드를 설정할 수 있지만, 기본은 안전 모드이다.

페이지에 명시되지 않은 내용

  • 출력 품질은 연결된 모델에 거의 전적으로 좌우된다. Cline은 공급자에 구애받지 않으며, Anthropic의 Claude, OpenRouter, GPT‑4o, 로컬 Ollama 모델 등 원하는 어떤 모델도 사용할 수 있다.
  • 이 유연성은 하나의 공급자에 묶이는 도구보다 큰 장점이지만, 선택한 모델에 따라 전혀 다른 경험을 할 수 있다는 뜻이다.

내가 설명할 실험에서는 Anthropic API를 직접 호출한 claude-3-5-sonnet을 사용했다. 모델 변수를 격리하고 싶었기 때문에 이번에는 OpenRouter를 사용하지 않았다.

프로젝트 개요

  • 스택: Express, Prisma, PostgreSQL을 사용하는 TypeScript 코드베이스
  • 특징: 실험적인 요소가 없으며, 알려진 스택을 선택해 Cline의 오류를 기술 스택에 대한 불확실성과 혼동하지 않도록 했다.

작업 분류

작업을 시작하기 전에 세 가지 카테고리로 나눴다.

  • Category A — 최종 검토만 하는 완전 위임

    • 기존 Prisma 스키마에서 Zod 타입 생성
    • 이미 구현된 순수 함수에 대한 단위 테스트 작성
    • 일관된 테스트 데이터를 가진 시드 파일 생성
  • Category B — 중간 체크포인트가 있는 위임

    • 결합도가 높은 검증 모듈 리팩터링
    • Express 엔드포인트를 더 깔끔한 라우터 구조로 마이그레이션
    • 특정 파일의 TypeScript 엄격 모드 오류 해결
  • Category C — 위임하지 않고 모니터링

    • 데이터베이스 스키마 변경
    • 인증 로직 수정
    • 인프라 구성 파일 변경

이 분류는 처음 48시간 사용 후에 만든 것이다. Category B 작업 중 Cline이 내가 언급하지 않은 파일의 import를 바꾸면서 타입 오류를 해결했는데, 이는 기술적으로는 맞았지만 내 컨텍스트를 완전히 벗어나게 만들었다. 심각한 오류는 아니었지만, “끝에서 검토” 방식이 횡방향 의존성이 있는 작업에는 맞지 않음을 알려주는 신호였다.


Category A에 잘 맞는 명령 예시

// 기존 Prisma 모델에서 Zod 스키마를 생성하는 예시

// Cline에게 전달할 명령:
// "Generate a Zod schema for the User model from the schema.prisma file.
// Only the file src/schemas/user.schema.ts.
// Do not modify any other file.
// Use z.string().uuid() for the id field."

// 기대한 결과와 실제 결과:
import { z } from 'zod'

export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  nombre: z.string().min(1),
  creadoEn: z.coerce.date(),
  actualizadoEn: z.coerce.date(),
})

export type User = z.infer<typeof UserSchema>

명령의 정확성이 작업 복잡도보다 더 중요하다는 것을 처음에 배웠다.


오류 1: 로컬 수정의 과도한 일반화

특정 파일에서 TypeScript 오류를 해결해 달라고 요청했더니, Cline은 오류를 정확히 고쳤지만 공유 타입 정의 파일까지 “더 깔끔하게” 수정했다. 기술적으로는 완벽했지만, 내 입장에서는 컨텍스트가 완전히 사라졌다.

드러난 점: Cline은 전체 코드베이스를 고려한다. “파일 X 밖에서는 수정하지 말라”는 명시가 없으면 옆으로도 탐색한다. 이는 근본 원인을 찾고 싶을 때는 장점이지만, 정밀한 수술적 변경을 원할 때는 문제다.


오류 2: 통과했지만 쓸모 없는 테스트

테스트 생성 작업에서 Cline은 100% 커버리지를 달성한 파일을 제공했지만, 실제로는 구현을 테스트하는 수준이었다. 예를 들어 expect(fn()).toBeDefined() 같은 식으로 행동이 아닌 구현만 검증했다.

드러난 점: “이 함수에 대한 테스트를 작성해라”는 명령이 너무 포괄적이다. 어떤 엣지 케이스를 포함시킬지, 핵심 행동은 무엇인지, 어떤 수준의 어설션을 기대하는지를 명시해야 한다. 명시하지 않으면 Cline은 커버리지를 최적화할 뿐, 실제 유용성을 고려하지 않는다.

// 모호한 명령 → 통과하지만 쓸모 없는 테스트
// "Write tests for the calcularDescuento function"

describe('calcularDescuento', () => {
  it('should return a value', () => {
    // ← 실제로는 아무것도 검증하지 않음
    expect(calcularDescuento(100, 10)).toBeDefined()
  })
})
// 정확한 명령 → 실제로 의미 있는 테스트
// "Write tests for calcularDescuento.
// Required cases:
// - 0% discount returns the original price unchanged
// - 100% discount returns 0
// - negative discount throws an Error with message 'Descuento inválido'
// - price 0 with any discount returns 0"

describe('calcularDescuento', () => {
  it('0% discount returns original price', () => {
    expect(calcularDescuento(100, 0)).toBe(100)
  })
  it('100% discount returns 0', () => {
    expect(calcularDescuento(100, 100)).toBe(0)
  })
  it('negative discount throws error', () => {
    expect(() => calcularDescuento(100, -5)).toThrow('Descuento inválido')
  })
  it('price 0 returns 0 regardless of discount', () => {
    expect(calcularDescuento(0, 50)).toBe(0)
0 조회
Back to Blog

관련 글

더 보기 »