File-based Planning Workflow 파트 1: 스펙과 계획으로 코딩 에이전트와 협업하기

아샬

문제: AI는 추측해서 만듭니다

코딩 에이전트에게 "로그인 기능 만들어주세요"라고 요청하면 무슨 일이 벌어질까요?

코딩 에이전트는 추측해서 만듭니다.

  • 이메일 로그인인지, 소셜 로그인인지
  • 세션 기반인지, JWT인지
  • 실패하면 계정 잠금이 있는지 없는지
  • 패스워드 재설정은 어떻게 할지

우리가 정하지 않은 것을 AI가 정합니다.
그 결과는 우리가 원하는 것과 다를 확률이 높습니다.

해법: 스펙을 먼저 명시합니다

코딩 에이전트와 작업할 때 가장 먼저 할 일은 무엇을 만들지 명확히 하는 것입니다.

FBPW(File-based Planning Workflow)는 사람과 AI 모두 읽고 쓸 수 있는 파일로 이 문제를 해결합니다.1

specs/login-feature/
├── README.md       # 개요
├── spec.md         # 요구사항 (스펙)
├── plan.md         # 구현 계획
├── tasks.md        # 작업 목록
├── findings.md     # 발견과 결정
└── progress.md     # 진행 기록

1단계: README.md — 배경과 목표

왜 이 기능이 필요한지, 무엇을 달성하려 하는지, 어떻게 동작하는지를 간단히 정리합니다.

프로젝트의 전체 그림을 한눈에 파악할 수 있습니다.

# 사용자 로그인

## Background

현재 인증 없이 모든 API가 열려 있습니다.
사용자별 데이터 보호가 필요합니다.

## Goal

이메일/패스워드 기반 로그인 시스템을 구축하여 사용자 인증을 제공합니다.

## How it works

1. 사용자가 이메일/패스워드 입력
2. 서버가 JWT 발급
3. 클라이언트가 JWT를 헤더에 포함해 API 요청

2단계: spec.md — 요구사항 정의

이 파일이 가장 중요합니다.

사용자 스토리, 인수 시나리오, 기능 요구사항, 제약사항, 성공 기준을 명확히 적습니다.

AI 도구로 초안을 작성하고, 리뷰하고, 수정 요청하는 작업을 반복해서 완성해 나갑니다.

# Feature Specification: 사용자 로그인

## User Scenarios & Testing (mandatory)

### User Story 1: 로그인 성공

- As: 가입한 사용자로서
- I: 이메일/패스워드로 로그인할 수 있습니다
- So: 인증이 필요한 기능을 사용하기 위해

#### Acceptance Scenarios

Scenario 1: **이메일과 패스워드가 올바를 때**

- Given: 이메일 `user@example.com`, 패스워드 `pass123`인 사용자가 있을 때
- When: 해당 이메일/패스워드로 로그인 요청하면
- Then: JWT 토큰이 반환됩니다

### User Story 2: 로그인 실패

Scenario 1: 패스워드가 틀렸을 때

- Given: 가입된 이메일이 있을 때
- When: 잘못된 패스워드로 로그인 시도하면
- Then: 401 에러와 "이메일 또는 패스워드가 올바르지 않습니다" 메시지 반환됩니다

## Functional Requirements (mandatory)

- FR-1: MUST 이메일/패스워드 기반 로그인
- FR-2: MUST JWT 토큰 발급 (유효기간 24시간)
- FR-3: MUST 로그인 5회 실패하면 계정 10분 잠금
- FR-4: SHOULD 패스워드 재설정 (이메일 링크)

## Constraints (mandatory)

- CON-1: MUST Argon2로 패스워드 해싱
- CON-2: MUST 토큰은 httpOnly 쿠키로 전달

## Success Criteria (mandatory)

- SC-1: 올바른 이메일, 패스워드로 로그인하면 JWT 발급
- SC-2: JWT로 보호된 API 접근 가능
- SC-3: 5회 실패하면 10분 잠금 동작 확인

여기까지 스펙을 작성했으면 바로 구현을 요청해도 됩니다.
우리는 개발자니까, 사전 기술 검토를 위해 구현 계획도 세워봅니다.

3단계: plan.md — 구현 계획

spec.md를 바탕으로 어떻게 만들지 계획을 세웁니다.

주요 구현 방식, 주요 파일 목록, 아키텍처, 구현 단계를 작성합니다.

# Implementation Plan: 사용자 로그인

## Summary

Kotlin + Spring Boot로 JWT 기반 인증 시스템을 구현합니다.
Outside-In TDD로 개발합니다 (Controller → Application Service → Domain Model).

## Requirements

1. 이메일/패스워드 로그인
2. JWT 발급 (24시간 유효)
3. 5회 실패하면 10분 잠금

## Critical Files

### New Files

- `src/main/kotlin/.../controller/SessionController.kt` — API 엔드포인트
- `src/main/kotlin/.../application/LoginService.kt` — 로그인 유스케이스 조율
- `src/main/kotlin/.../models/User.kt` — 사용자 모델
- `src/main/kotlin/.../models/JwtService.kt` — JWT 도메인 서비스

### Modified Files

- `build.gradle.kts` — jjwt, spring-security-crypto 의존성 추가

### Reference Files

- `src/main/kotlin/.../config/SecurityConfig.kt` — 기존 보안 설정

## Architecture

### User Flow

```text
POST /api/session
  → SessionController
  → LoginService
  → User.verifyPassword()
  → JwtService.generateToken()
  → JWT 반환
```

## Implementation Steps

### Step 1: SessionController (UI Layer)

**Red:** `SessionControllerTest.kt`

```kotlin
// POST /api/session 요청 → JWT 응답 테스트
```

**Green:** `SessionController.kt`

```kotlin
@RestController
class SessionController(private val loginService: LoginService) {
    @PostMapping("/api/session")
    @ResponseStatus(HttpStatus.CREATED)
    fun create(@RequestBody request: LoginRequest): String
}
```

### Step 2: LoginService (Application Layer)

**Red:** `LoginServiceTest.kt`

```kotlin
// 로그인 성공/실패, 계정 잠금 테스트
```

**Green:** `LoginService.kt`

```kotlin
@Service
class LoginService(private val userRepository: UserRepository) {
    fun login(email: String, password: String): String
}
```

### Step 3: User (Domain Layer)

**Red:** `UserTest.kt`

```kotlin
// 패스워드 해싱(Argon2), 검증 테스트
```

**Green:** `User.kt`

```kotlin
@Entity
class User(val email: String) {
    fun hashPassword(password: String): String
    fun verifyPassword(password: String): Boolean
}
```

## Verification

### Build

```bash
./gradlew build
```

### Test

```bash
./gradlew test
```

### Manual Test

```bash
curl -X POST http://localhost:8080/api/session \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"pass123"}'
```

왜 파일인가요?

데이터베이스도, 클라우드도 아닌 마크다운 파일을 쓰는 이유:

  1. 코딩 에이전트가 읽고 쓸 수 있습니다 → 추가 도구 불필요
  2. Git으로 버전 관리합니다 → 변경 이력 추적
  3. 사람도 쉽게 편집합니다 → IDE에서 바로 수정
  4. 인프라가 불필요합니다 → 파일 시스템이면 충분

파일 시스템은 사람과 코딩 에이전트가 함께 읽고 쓸 수 있는 가장 단순한 저장소입니다.

정리

파트 1에서 다룬 내용:

  1. 스펙을 먼저 명시합니다 (spec.md) → AI가 추측하지 않도록
  2. 계획을 뽑습니다 (plan.md) → 구현 전에 방향 설정

구현하면서 맥락을 유지하는 방법(tasks.md, findings.md, progress.md)은 파트 2에서 다룹니다.


템플릿: https://github.com/ahastudio/file-based-planning-workflow

실제 예제: https://github.com/ahastudio/CodingLife/tree/main/20260213/react/specs/calendar

Footnotes

  1. File-based Planning Workflow(FBPW)는 코딩 에이전트와의 협업을 위한 파일 기반 패턴입니다. 스펙 명시와 기록을 통해 체계적인 개발을 가능하게 합니다.