오늘도 공부
보리스의 How I Use Claude Code 본문
Boris Tane
The research-plan-implement workflow I use to build software with Claude Code, and why I never let it write code until I've approved a written plan.
boristane.com
아래는 원문( Boris Tane, “How I Use Claude Code” )을 그대로 문장 단위로 전체를 다시 정리한 내용입니다. 대신 원문에 나온 **프롬프트 패턴(명령 문장)**은 실무에서 바로 쓸 수 있게 한국어 템플릿으로 촘촘히 풀어쓰고, 이해를 돕는 **예제 코드/파일 템플릿(plan.md, research.md)**까지 넣었습니다. 원문의 핵심 원칙(계획-실행 분리, research→plan→annotate→todo→implement 등)은 그대로 따릅니다. (boristane.com)
Claude Code를 “기획(Planning)과 실행(Execution) 분리”로 쓰는 법
research.md → plan.md → 주석(annotate) → todo → implement (실전 템플릿 + 예제 코드 포함)
AI 코딩 에이전트를 쓰다 보면 가장 흔한 실패는 “코드가 돌아가긴 하는데, 프로젝트 전체의 맥락을 깨먹는” 형태입니다. 캐싱 레이어를 무시하거나, 기존 컨벤션을 어기거나, 이미 있는 로직을 중복 구현하는 식이죠. Boris Tane의 방식은 이 실패를 정면으로 막습니다. 핵심은 단 하나:
내가 승인한 ‘문서화된 계획(plan)’ 없이는 절대 코드를 쓰게 하지 않는다. (boristane.com)
이 글에서는 그 워크플로우를 한국어로 아주 구체화해서, 그대로 복붙해도 굴러가도록 정리합니다.
전체 워크플로우 한 장 요약
흐름은 이렇게 고정입니다:
- Research: 관련 폴더/흐름을 깊게 읽고 research.md로 남긴다
- Plan: 실제 코드베이스에 근거한 plan.md 구현 계획을 만든다
- Annotate: 내가 plan에 인라인 메모를 달고, Claude가 plan을 수정한다 (1~6회 반복)
- Todo: plan에 체크리스트(todo)를 붙여 진행상황 트래킹
- Implement: “이제 전부 구현”을 한 번에 실행 + 타입체크 지속 (boristane.com)
“생각(설계)과 타이핑(구현)을 분리”하는 파이프라인이 전부입니다. (boristane.com)
Claude Code 시작 세팅 (터미널 기준)
Claude Code는 터미널에서 실행하는 에이전틱 도구로, 코드베이스를 읽고 파일을 수정하고 커맨드를 실행할 수 있습니다. (Claude Code)
설치/실행은 공식 문서 기준으로:
# macOS / Linux / WSL
curl -fsSL https://claude.ai/install.sh | bash
# Homebrew
brew install --cask claude-code
# Windows (PowerShell)
irm https://claude.ai/install.ps1 | iex
설치 후 프로젝트 폴더에서:
cd your-project
claude
첫 실행 시 로그인 안내가 나오고, 필요하면 세션 내에서 /login도 사용합니다. (Claude Code)
“매번 반복할 규칙”은 CLAUDE.md로 고정해두기 (강추)
Claude Code는 시작 시 **메모리 파일 CLAUDE.md**를 로드해 “항상 적용되는 지침/컨텍스트”로 쓸 수 있습니다. 또한 내부 시스템 프롬프트는 공개되지 않으며, 커스텀 지침을 추가하려면 CLAUDE.md 또는 --append-system-prompt를 쓰라고 문서가 안내합니다. (Claude Code)
프로젝트 루트에 CLAUDE.md를 만들고 아래를 넣어두면, 이후부터는 매 세션마다 “don’t implement yet” 같은 안전장치를 덜 반복해도 됩니다.
✅ CLAUDE.md 템플릿 (Boris 방식에 최적화)
# Working Agreement (Claude Code)
## 절대 규칙
- 내가 "IMPLEMENT" 또는 "implement it all"이라고 명시하기 전까지는 코드/파일 수정(구현)을 시작하지 마.
- 항상 다음 순서로 진행해:
1) research.md 작성
2) plan.md 작성
3) plan.md에 내가 단 인라인 노트 모두 반영(1회 이상)
4) plan.md에 todo 체크리스트 추가
5) 구현 시작
## 산출물 규칙
- research 결과는 반드시 research.md 파일로 남겨.
- 구현 계획은 반드시 plan.md 파일로 남겨(변경 파일 경로, 코드 스니펫, 트레이드오프 포함).
- 구현 중에는 plan.md의 todo를 완료 표시로 업데이트해.
## 품질 규칙
- 불필요한 주석/JSDoc은 추가하지 마(요청이 있거나 공개 API가 아닌 이상).
- any/unknown 타입 사용 금지. 필요하면 타입을 설계해.
- 작업 중간중간 typecheck/test를 실행해 새 이슈를 조기에 잡아.
팁: 민감 파일은 .claude/settings.json에서 permissions.deny로 읽기 자체를 막을 수 있습니다(예: .env). (Claude Code)
Phase 1: Research — “깊게 읽고 research.md로 남겨라”
Boris의 1단계는 무조건 딥리드 지시 + 문서 산출물입니다. 채팅으로 요약만 받지 않고, 반드시 research.md에 적게 합니다. 그래야 내가 검토하고 오해를 바로잡을 수 있기 때문입니다. (boristane.com)
Research 프롬프트 템플릿 (한국어)
아래 중 하나를 골라 쓰면 됩니다.
1) 폴더 단위 딥리드
“<폴더경로>를 깊게 읽고(겉핥기 금지) 어떤 역할을 하는지/예외 케이스/컨벤션/의존성을 포함해 완전히 이해해. 끝나면 research.md에 상세 리포트를 작성해.”
2) 특정 시스템(예: 알림/결제/스케줄러) 딥리드
“<시스템명> 동작을 아주 자세히 분석해. 흐름도처럼 단계별로 설명하고, 관련 파일 목록/핵심 함수/데이터 모델/실패 모드를 포함해서 research.md로 정리해.”
3) 버그 헌트(“끝까지 찾아라” 스타일)
“<흐름명>을 끝까지 추적해서 ‘취소되어야 할 작업이 실행되는’ 종류의 버그 가능성을 찾아. 원인 후보를 나열하지 말고, 코드로 근거를 찾아서 확정해. 모두 찾을 때까지 멈추지 말고, 결과는 research.md에 써.”
이 단계에서 Boris는 “deeply / in great detail” 같은 표현을 의도적으로 쓰는 게 중요하다고 말합니다. 대충 읽고 지나가려는 성향을 막는 신호라는 거죠. (boristane.com)
research.md 예시 뼈대 (복붙용)
# Research: <대상 시스템/폴더>
## 1) 한 문장 요약
- 이 시스템은 <…>을 한다.
## 2) 핵심 엔트리포인트
- 파일: path/to/file.ts
- 함수/클래스: Foo
- 호출 흐름: A -> B -> C
## 3) 데이터 모델 / 상태
- Table/Schema:
- 주요 필드 의미:
- 불변 조건(invariants):
## 4) 정상 플로우(단계별)
1.
2.
3.
## 5) 예외/에러/리트라이/타임아웃
- 케이스:
- 현재 동작:
- 잠재 리스크:
## 6) 관련 파일 맵
- path/...
- path/...
## 7) 수정 시 주의해야 할 컨벤션
- 예: “migrations는 drizzle:generate로…”
- 예: “PATCH vs PUT 규칙…”
## 8) 발견한 버그/부채(있다면)
- 증상:
- 원인 코드:
- 재현 방법:
- 고치려면:
Phase 2: Planning — plan.md를 “실제 구현 설계서”로 만들기
research를 검토하고 나면, 이제 “어떻게 바꿀지”를 plan.md로 뽑습니다. Boris는 plan이 설명 + 변경 파일 경로 + 코드 스니펫 + 트레이드오프를 포함해야 한다고 강조합니다. (boristane.com)
또한 Claude Code의 내장 plan 모드 대신, 내가 직접 편집 가능한 md 파일을 선호한다고 말합니다(내 에디터에서 메모 달고, 프로젝트에 아티팩트로 남기기 좋기 때문). (boristane.com)
Planning 프롬프트 템플릿 (한국어)
1) 신규 기능 추가
“<기능명>을 만들고 싶어. 목적은 <비즈니스 결과>야. 위 research.md를 바탕으로, 실제 코드 구조에 맞게 plan.md에 구현 계획을 써줘. 변경될 파일 경로, 구체 스니펫, 고려사항/트레이드오프 포함.”
2) 기존 기능 교체(예: offset → cursor pagination)
“<엔드포인트/기능>을 <A방식>에서 <B방식>으로 바꿔야 해. 실제 소스 파일을 읽고(추측 금지), 코드베이스에 맞는 plan.md를 작성해. 변경 파일 경로와 스니펫을 포함해.”
3) “좋은 오픈소스 구현”을 참고시키는 패턴
Boris는 “비슷한 기능을 잘 구현한 레퍼런스 코드”를 함께 주면 성능이 좋아진다고 합니다(처음부터 설계하게 두기보다 구체 구현을 기준점으로 삼게 하는 것). (boristane.com)
“아래는 <기능>을 잘 구현한 레퍼런스 코드야. 이 접근을 우리 코드베이스에 적용하는 plan.md를 작성해줘(단, 우리 컨벤션/구조를 따를 것).”
plan.md 템플릿 (복붙용)
# Plan: <기능/버그fix 제목>
## 목표(Goals)
- G1:
- G2:
## 비목표(Non-goals)
- N1:
## 현상/배경(Research 요약)
- (research.md 핵심을 5~10줄로)
## 변경 범위(파일 단위)
- path/to/fileA.ts
- path/to/fileB.ts
- path/to/ui/Component.tsx
## 설계(Approach)
- 선택한 접근:
- 대안:
- 트레이드오프:
## API/DB/스키마 변경(있다면)
- API:
- Schema:
- Migration:
## 구현 스니펫(핵심만)
```ts
// before
// after
테스트/검증 계획
- unit:
- integration:
- e2e:
- typecheck:
롤백/안전장치
- feature flag 여부:
- 배포 시 주의:
TODO (체크리스트는 나중에 추가)
- (빈칸)
---
# The Annotation Cycle — plan.md에 인라인 메모 달고 “계획을 고친 뒤”에만 구현
여기부터가 Boris 방식의 핵심입니다. plan.md를 Claude가 작성하면, 내가 에디터에서 **인라인 메모를 직접** 달고, Claude에게 “메모 반영해서 plan 업데이트, 아직 구현 금지”를 반복합니다. 1~6회 반복도 흔하다고 합니다. :contentReference[oaicite:13]{index=13}
## Annotate 프롬프트 템플릿 (한국어)
> “plan.md에 인라인 노트를 몇 개 추가했어. **노트 전부를 빠짐없이 반영**해서 plan.md를 업데이트해줘. **아직 구현하지 마.**”
Boris는 “아직 구현하지 마(don’t implement yet)”를 명시하지 않으면, 모델이 “이 정도면 됐지?” 하고 구현으로 뛰어드는 성향이 있다고 강조합니다. :contentReference[oaicite:14]{index=14}
## plan.md에 다는 인라인 노트 예시(한국어 버전)
원문에 등장하는 노트 사례를 “형태”만 살려 한국어로 바꾸면 이런 톤입니다. :contentReference[oaicite:15]{index=15}
- “이건 **옵션 아님(필수)**”
- “마이그레이션은 raw SQL 말고 **drizzle generate**로” (팀 컨벤션 주입)
- “PUT 말고 PATCH가 맞음”
- “여기 캐싱 필요 없음. 섹션 삭제”
- “이미 컨슈머가 리트라이 처리하니 이 리트라이는 중복. 제거”
- “이 필드는 아이템이 아니라 리스트에 있어야 함. 스키마 섹션 전체 재구성”
요점은 **채팅으로 길게 설명하지 말고**, plan 문서의 “문제가 있는 바로 그 위치”에 주석으로 박아넣는 겁니다. :contentReference[oaicite:16]{index=16}
---
# Todo List — plan을 “진행판”으로 만들기
plan이 만족스러워지면, 구현 전에 todo 체크리스트를 붙입니다. 구현 중 Claude가 완료 표시를 하게 해서 “지금 어디까지 했지?”를 plan만 봐도 알 수 있게 합니다. :contentReference[oaicite:17]{index=17}
## Todo 프롬프트 템플릿 (한국어)
> “plan.md에 **단계별로 쪼갠 상세 TODO 체크리스트**를 추가해줘(phase + task 단위). **아직 구현하지 마.**” :contentReference[oaicite:18]{index=18}
---
# Phase 3: Implementation — “이제 전부 구현” 단 한 번의 명령으로 끝까지 달리기
마지막에 Boris가 쓰는 표준 구현 프롬프트는 이런 요구를 담습니다:
- plan에 있는 걸 **전부** 구현
- 작업이 끝날 때마다 plan에서 완료 체크
- 중간에 멈추지 말기
- 불필요한 주석/JSDoc 금지
- any/unknown 금지
- typecheck 계속 돌리기 :contentReference[oaicite:19]{index=19}
## Implement 프롬프트 템플릿 (한국어)
> “**plan.md에 적힌 내용을 전부 구현해.**
> 각 task/phase가 끝날 때마다 plan.md의 TODO를 완료로 표시해.
> **모든 task가 끝날 때까지 멈추지 마.**
> 불필요한 주석/JSDoc은 추가하지 마.
> `any`/`unknown` 타입을 쓰지 마.
> 작업 중간중간 **typecheck를 계속 실행**해서 새 이슈가 생기지 않게 해.”
이 “단일 프롬프트”는 이후 구현이 창의 작업이 아니라 “기계 작업”이 되도록 만드는 장치라고 설명합니다. :contentReference[oaicite:20]{index=20}
---
# 구현 중 피드백은 “짧게”, 틀어지면 “리버트 후 스코프 축소”
구현이 시작되면 내 역할은 “설계자”에서 “감독”으로 바뀝니다. 이때는 긴 메시지보다 한 문장 교정이 효과적이라고 합니다. :contentReference[oaicite:21]{index=21}
- “`deduplicateByTitle` 함수 빠졌어. 추가해.”
- “설정 페이지를 메인 앱에 만들었는데 admin 앱이 맞아. 옮겨.”
프론트엔드는 브라우저에서 보며 “wider / still cropped / 2px gap”처럼 아주 짧은 피드백을 반복하고, 필요하면 스크린샷 첨부가 효율적이라고도 말합니다. :contentReference[oaicite:22]{index=22}
그리고 방향이 잘못되면 패치로 땜질하지 말고:
> “다 리버트했어. 이제 목표는 딱 ‘리스트 뷰를 더 미니멀하게’ 이것뿐이야.”
처럼 **리버트 후 스코프를 좁히는 게 더 낫다**고 합니다. :contentReference[oaicite:23]{index=23}
---
# 긴 세션 운영(Research→Plan→Implement를 한 세션에서)
Boris는 research/planning/implementation을 세션으로 쪼개지 않고 “한 번 길게” 간다고 말합니다. 그리고 컨텍스트가 차도 자동 컴팩션이 버텨주며, plan.md 같은 “지속 아티팩트”가 남아 있어서 계속 참조 가능하다고 설명합니다. :contentReference[oaicite:24]{index=24}
---
# (실전 예제) offset 페이지네이션을 cursor 페이지네이션으로 바꾸기
### 프롬프트 + plan 템플릿 + 예제 코드까지 “한 방에 따라하기”
아래는 많은 백엔드에서 자주 하는 작업을 예로 들어, **이 워크플로우를 실제로 굴리는 모습**을 보여드립니다. (예제 코드는 이해를 돕기 위한 샘플이며 원문 코드 번역이 아닙니다.)
## 상황 가정
- Node.js + TypeScript + Drizzle ORM
- 기존: `GET /api/tasks?offset=0&limit=20`
- 목표: `GET /api/tasks?cursor=<token>&limit=20`
응답에 `nextCursor` 포함 (무한 스크롤/“더 보기”에 유리)
---
## 1) Research 단계: Claude에게 “관련 폴더/흐름”을 깊게 읽히기
### Research 프롬프트(복붙)
> “`src/routes/tasks`와 `src/services/tasks`, `src/db/schema`를 깊게 읽고, 현재 list endpoint가 어떻게 offset pagination을 구현하는지 완전히 이해해. 예외 케이스(정렬 안정성, 동시성, 중복/누락 가능성)까지 포함해서 `research.md`에 상세 보고서를 작성해.”
### 기대하는 research.md 핵심 포인트(예시)
- 현재 정렬 기준이 무엇인지(예: `createdAt DESC`)
- offset이 커질수록 성능 저하 가능성
- 정렬이 안정적이지 않으면(동일 createdAt 다수) 페이지 이동 시 중복/누락 가능성
- 서비스/라우트/DB 레이어 분리 구조
---
## 2) Plan 단계: 실제 파일 경로 + 스니펫 + 트레이드오프가 있는 plan.md 만들기
### Plan 프롬프트(복붙)
> “현재 offset 기반인 tasks list endpoint를 cursor 기반으로 바꿔야 해. research.md를 바탕으로, 실제 코드베이스에 맞게 `plan.md`를 작성해. 변경 파일 경로, 핵심 코드 스니펫, 트레이드오프(정렬 안정성/커서 인코딩 방식/역호환)도 포함해.”
### plan.md에 꼭 들어가야 할 결정들(강제 체크리스트)
- 커서 기준 필드: `createdAt`만 쓰면 불안정 → 보통 `(createdAt, id)` 복합으로 안정화
- 커서 토큰 형식: base64(json) vs 암호화 토큰
- `limit` 상한(예: max 100)
- 응답 형식: `{ items, nextCursor }`
- 기존 offset endpoint를 유지할지(Deprecated 기간)
---
## 3) Annotate 단계: 내가 plan.md에 “결정 주입”하기
plan.md를 열고 이런 인라인 노트를 달아보세요:
- “cursor는 `(createdAt, id)` 복합으로 안정 정렬. **createdAt만 쓰면 안 됨**”
- “limit 최대 100”
- “기존 offset 파라미터는 2주간 유지(경고 로그만), 이후 제거”
- “마이그레이션/스키마 변경은 없음”
- “테스트: 중복/누락 없음을 케이스로 검증”
그 다음 Claude에게:
> “plan.md에 단 노트 전부 반영해서 plan을 업데이트해. 아직 구현하지 마.”
---
## 4) Todo 추가
> “plan.md에 단계별 상세 TODO 체크리스트 추가. 아직 구현하지 마.”
예시 TODO(샘플):
```md
## TODO
- [ ] (API) cursor 토큰 encode/decode 유틸 추가
- [ ] (Service) cursor 조건(where) + 안정 정렬(orderBy) + limit+1 구현
- [ ] (Route) 쿼리 파라미터 스키마(zod 등) 업데이트
- [ ] (Response) nextCursor 계산 및 반환
- [ ] (Compat) offset 파라미터 deprecated 처리(옵션)
- [ ] (Tests) 중복/누락/정렬 안정성 테스트
- [ ] (Typecheck) 타입체크/린트 통과
5) Implement 단계: 한 번에 끝까지 달리기
“plan.md에 적힌 내용을 전부 구현해…(중략)… typecheck 계속 실행…”
예제 코드 (TypeScript + Drizzle) — cursor pagination 핵심만
아래는 “서비스 레이어”에서 cursor pagination을 구현하는 가장 전형적인 형태 예시입니다.
1) cursor 유틸
// src/lib/cursor.ts
export type TaskCursor = {
createdAt: string; // ISO string
id: string;
};
export function encodeCursor(cursor: TaskCursor): string {
const json = JSON.stringify(cursor);
return Buffer.from(json, "utf8").toString("base64url");
}
export function decodeCursor(token: string): TaskCursor {
const json = Buffer.from(token, "base64url").toString("utf8");
const parsed = JSON.parse(json) as Partial<TaskCursor>;
if (!parsed.createdAt || !parsed.id) {
throw new Error("Invalid cursor");
}
return { createdAt: parsed.createdAt, id: parsed.id };
}
2) Drizzle 쿼리: (createdAt, id) 기반 안정 페이징
// src/services/taskService.ts
import { and, desc, eq, lt, or } from "drizzle-orm";
import { db } from "../db";
import { tasks } from "../db/schema";
import { decodeCursor, encodeCursor, TaskCursor } from "../lib/cursor";
type ListTasksInput = {
limit?: number;
cursor?: string;
};
export async function listTasks(input: ListTasksInput) {
const limit = Math.min(input.limit ?? 20, 100);
let cursor: TaskCursor | null = null;
if (input.cursor) cursor = decodeCursor(input.cursor);
// 안정 정렬: createdAt DESC, id DESC
// 다음 페이지 조건: (createdAt, id) < (cursor.createdAt, cursor.id)
const where =
cursor == null
? undefined
: or(
lt(tasks.createdAt, new Date(cursor.createdAt)),
and(
eq(tasks.createdAt, new Date(cursor.createdAt)),
lt(tasks.id, cursor.id),
),
);
const rows = await db
.select({
id: tasks.id,
title: tasks.title,
createdAt: tasks.createdAt,
})
.from(tasks)
.where(where)
.orderBy(desc(tasks.createdAt), desc(tasks.id))
.limit(limit + 1); // nextCursor 계산용으로 1개 더 가져오기
const hasNext = rows.length > limit;
const items = hasNext ? rows.slice(0, limit) : rows;
const nextCursor = hasNext
? encodeCursor({
createdAt: items[items.length - 1]!.createdAt.toISOString(),
id: items[items.length - 1]!.id,
})
: null;
return { items, nextCursor };
}
3) Route 응답 예시
// src/routes/tasks.ts (예: Fastify/Express 핸들러 내부)
const { items, nextCursor } = await listTasks({
limit: Number(req.query.limit ?? 20),
cursor: req.query.cursor,
});
res.json({ items, nextCursor });
“복붙용 프롬프트 모음” (이 글의 핵심)
아래는 진짜로 매번 쓰게 되는 문장들입니다.
A. Research
- “<폴더>를 깊게 읽고, 작동/예외/컨벤션/의존성까지 완전히 이해한 뒤 research.md에 상세 보고서 작성.”
- “<시스템>을 아주 자세히 분석하고 research.md에 ‘흐름+파일맵+실패모드’까지 포함해 정리.”
B. Plan
- “research.md를 바탕으로 plan.md 작성. 변경 파일 경로 + 스니펫 + 트레이드오프 포함.”
- “추측 금지. 실제 소스 읽고 plan 작성.”
C. Annotate
- “plan.md에 단 인라인 노트 전부 반영해서 업데이트. 아직 구현하지 마.”
D. Todo
- “plan.md에 단계별 상세 TODO 체크리스트 추가. 아직 구현하지 마.”
E. Implement
- “plan.md대로 전부 구현. 각 task 끝날 때 plan에 완료 표시. 끝까지 멈추지 마. 불필요한 주석 금지. any/unknown 금지. typecheck 계속.”
(이 패턴 자체가 원문에서 강조하는 ‘표준 프롬프트’의 요지입니다.) (boristane.com)
덧붙임: CLI에서 “행동 규칙”을 더 강하게 걸고 싶다면
Claude Code는 시스템 프롬프트를 커스터마이즈하는 플래그들을 제공합니다. 예를 들어 --append-system-prompt로 기본 동작은 유지하면서 추가 지침을 붙일 수 있습니다. (Claude Code)
claude --append-system-prompt "Never implement before plan.md is approved. Always write research.md then plan.md first."
(팀 단위로는 CLAUDE.md를 버전관리하는 쪽이 보통 더 깔끔합니다. (Claude Code))
'AI > Claude code' 카테고리의 다른 글
| Opus 4.6 1000K 토큰 적용 방법 (3) | 2026.02.06 |
|---|---|
| Slack-Claude Gateway: Slack에서 Claude AI와 대화하기 (0) | 2026.02.04 |
| Claude Code로 하는 스펙 기반 개발 (Spec-Driven Development) 완전 가이드 (0) | 2026.02.02 |
| Claude Code 팀이 직접 공개한 실전 꿀팁 11가지 (1) | 2026.02.01 |
| Claude Code 완벽 마스터 가이드 V3: LSP, CLAUDE.md, MCP, Skills & Hooks (0) | 2026.01.26 |
