릴리즈 노트 자동화: Git 커밋 로그로 다국어 패치노트 생성하기
Conventional Commits와 GitHub Actions를 활용해 Git 커밋 로그를 정리하고 한국어·영어·일본어 패치노트를 자동 생성하는 실전 구성과 운영 기준을 소개합니다.

핵심 요약
Git 커밋 메시지를 Conventional Commits 1.0.0 형식으로 통일하면 커밋 로그에서 기능 추가·버그 수정·변경 사항을 안정적으로 분류할 수 있습니다. 이후 GitHub Actions가 릴리즈 범위의 커밋을 수집하고 템플릿에 맞춰 한국어·영어·일본어 패치노트를 생성하면 수작업 정리 시간을 줄이면서도 문서 품질과 배포 일정을 일정하게 유지할 수 있습니다.
핵심은 번역 모델에 전체 커밋 로그를 그대로 전달하는 것이 아니라 커밋 수집 → 분류 → 민감 정보 제거 → 초안 생성 → 담당자 검수 → 배포 단계를 분리하는 것입니다.
릴리즈 노트 자동화가 필요한 이유는 무엇인가?
게임 프로젝트에서는 하나의 버전에 클라이언트 코드와 서버 코드, 기획 데이터, 아트 리소스, 운영 설정이 함께 포함됩니다. 개발자는 커밋을 남기지만 플레이어가 이해할 수 있는 패치노트는 별도의 문서 언어로 작성해야 합니다.
수동 작성 방식은 다음 문제를 만들기 쉽습니다.
| 문제 | 실제 영향 | 자동화로 줄일 수 있는 부분 |
|---|---|---|
| 커밋 메시지가 제각각임 | 기능과 버그 수정의 분류가 어려움 | Conventional Commits 검증 |
| 개발 용어가 그대로 노출됨 | 플레이어가 변경 내용을 이해하기 어려움 | 사용자 관점 문장으로 변환 |
| 언어별 문서가 따로 작성됨 | 번역 누락과 표현 불일치 발생 | 동일한 변경 데이터에서 언어별 생성 |
| 릴리즈 직전 한꺼번에 작성함 | 검수 시간이 부족해짐 | Pull Request 단계에서 초안 생성 |
| 내부 정보가 포함될 수 있음 | 티켓 번호, 서버 주소, 실험명이 노출됨 | 공개 전 필터링과 승인 단계 |
자동화의 목표는 사람의 검수를 없애는 것이 아닙니다. 사람이 판단해야 할 부분을 남기고 반복적인 수집·분류·초안 작성 작업을 줄이는 것이 목표입니다.

어떤 커밋 규칙을 사용해야 하는가?
가장 실용적인 출발점은 Conventional Commits 1.0.0입니다. 커밋 제목에 변경 유형과 선택적인 스코프를 포함합니다.
type(scope): subject
게임 개발 프로젝트에서는 다음 유형을 사용할 수 있습니다.
| 타입 | 의미 | 패치노트 분류 예시 |
|---|---|---|
feat | 새로운 기능 | 신규 기능 |
fix | 버그 수정 | 버그 수정 |
balance | 밸런스 조정 | 밸런스 및 게임플레이 |
perf | 성능 개선 | 최적화 |
content | 스테이지·퀘스트·아이템 추가 또는 변경 | 콘텐츠 |
art | 그래픽·애니메이션·사운드 변경 | 그래픽 및 오디오 |
infra | 빌드·배포·개발 환경 변경 | 내부 개선 또는 비공개 |
docs | 문서 변경 | 기본적으로 제외 |
chore | 기타 유지보수 | 기본적으로 제외 |
예시는 다음과 같습니다.
feat(inventory): 장비 자동 정렬 기능 추가
fix(combat): 보스 보호막이 중복 적용되는 문제 수정
balance(hero): 마법사 3레벨 스킬 피해량 조정
content(chapter-04): 4장 보너스 퀘스트 추가
perf(rendering): 모바일 그림자 패스 드로우콜 감소
art(ui): 인벤토리 희귀도 아이콘 교체
커밋 메시지만으로 모든 변경 내용을 설명할 수는 없습니다. 따라서 제목은 짧게 유지하고 본문에 플레이어 영향과 검수 정보를 추가하는 방식이 좋습니다.
fix(combat): 보스 보호막이 중복 적용되는 문제 수정
플레이어 영향:
- 특정 협동 전투에서 보호막 피해 감소 효과가 두 번 적용되던 문제를 수정했습니다.
QA:
- 2인 협동 모드에서 보호막 1회 적용 여부 확인
- 일반 보스와 이벤트 보스 모두 확인
Release-Note: public
반대로 내부 구현만 변경된 커밋은 공개 대상에서 제외합니다.
refactor(matchmaking): 매치 큐 상태 객체 분리
Release-Note: internal
Git 커밋 로그를 어떻게 패치노트 데이터로 변환할까?
릴리즈 노트 자동화에서는 git log의 출력 형식을 먼저 안정적으로 정의해야 합니다. 사람이 보기 좋은 기본 출력보다 기계가 파싱하기 쉬운 구분자를 사용하는 편이 안전합니다.
git log v1.4.0..HEAD --no-merges --date=iso-strict --format='%H%x1f%an%x1f%ad%x1f%s%x1f%b%x1e'
각 필드는 ASCII 제어 문자를 기준으로 구분합니다.
%H: 전체 커밋 해시%an: 작성자 이름%ad: 작성 날짜%s: 제목%b: 본문%x1f: 필드 구분자%x1e: 커밋 레코드 구분자--no-merges: 병합 커밋 중복 제거
릴리즈 태그를 기준으로 범위를 계산하려면 다음과 같이 사용할 수 있습니다.
git log v1.3.0..v1.4.0 --no-merges --format='%H%x1f%s%x1f%b%x1e'
자동화에서는 HEAD보다 태그 간 비교가 재현성 측면에서 유리합니다. 같은 태그를 다시 처리해도 동일한 커밋 범위를 얻을 수 있기 때문입니다.
Python 3.12로 커밋을 분류하는 방법
다음 예시는 외부 패키지 없이 커밋 제목을 분석하고 공개 대상만 JSON으로 저장합니다. 실제 프로젝트에서는 팀의 커밋 규칙에 맞춰 정규식과 분류명을 조정하면 됩니다.
from __future__ import annotations
import json
import os
import re
import subprocess
from dataclasses import asdict, dataclass
from pathlib import Path
COMMIT_PATTERN = re.compile(
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s*(?P<subject>.+)$"
)
CATEGORY_MAP = {
"feat": "features",
"fix": "bug_fixes",
"balance": "balance",
"content": "content",
"art": "art_audio",
"perf": "improvements",
}
@dataclass
class Commit:
sha: str
author: str
date: str
type: str
scope: str | None
subject: str
body: str
breaking: bool
category: str
def get_commits(previous_tag: str, current_tag: str) -> list[Commit]:
command = [
"git", "log", f"{previous_tag}..{current_tag}", "--no-merges",
"--date=iso-strict",
"--format=%H%x1f%an%x1f%ad%x1f%s%x1f%b%x1e",
]
result = subprocess.run(command, check=True, capture_output=True, text=True)
commits: list[Commit] = []
for record in result.stdout.split("\x1e"):
fields = record.strip().split("\x1f")
if len(fields) < 5:
continue
sha, author, date, subject, body = fields[:5]
match = COMMIT_PATTERN.match(subject.strip())
if not match:
continue
commit_type = match.group("type")
category = CATEGORY_MAP.get(commit_type)
if category is None:
continue
commits.append(Commit(
sha=sha,
author=author,
date=date,
type=commit_type,
scope=match.group("scope"),
subject=match.group("subject").strip(),
body=body.strip(),
breaking=bool(match.group("breaking")),
category=category,
))
return commits
if __name__ == "__main__":
previous_tag = os.environ["PREVIOUS_TAG"]
current_tag = os.environ["CURRENT_TAG"]
data = [asdict(item) for item in get_commits(previous_tag, current_tag)]
Path("release-input.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
이 단계의 출력은 최종 패치노트가 아니라 번역과 문장 생성을 위한 입력 데이터입니다. 원문 커밋과 생성 결과를 분리하면 잘못된 문장을 쉽게 추적하고 재생성할 수 있습니다.
다국어 패치노트 생성 프롬프트는 어떻게 설계할까?
번역 모델 또는 사내 LLM을 사용할 때는 커밋을 그대로 번역하라고 지시하면 안 됩니다. 개발자용 표현을 플레이어용 표현으로 바꾸되 사실을 추가하지 않도록 출력 계약을 명확히 정의해야 합니다.
권장 입력 구조는 다음과 같습니다.
{
"version": "1.4.0",
"audience": "players",
"source_language": "ko",
"target_languages": ["ko", "en", "ja"],
"commits": [
{
"type": "fix",
"scope": "combat",
"subject": "보스 보호막이 중복 적용되는 문제 수정",
"body": "플레이어 영향: 특정 협동 전투에서 피해 감소 효과가 두 번 적용됨",
"category": "bug_fixes"
}
]
}
시스템 지침에는 다음 정책을 포함합니다.
너는 게임 릴리즈 노트 편집자다.
1. 입력에 없는 기능, 수치, 보상, 일정은 추가하지 않는다.
2. 커밋 해시, 작성자 이름, 내부 티켓 번호, 서버 주소는 출력하지 않는다.
3. 개발자 구현 용어를 플레이어가 이해할 수 있는 표현으로 바꾼다.
4. features, bug_fixes, balance, content, art_audio, improvements 순서를 유지한다.
5. 각 항목은 한 문장으로 작성하며 제목과 플레이어 영향이 겹치면 합친다.
6. 번역 언어마다 고유명사 용어집을 적용한다.
7. 확신할 수 없는 내용은 추측하지 말고 검수 필요 항목으로 표시한다.
8. 결과는 아래 JSON Schema만 반환한다.
출력 스키마를 고정하면 언어별 결과를 웹사이트, 런처, Steam 공지, 콘솔 플랫폼 문서에 재사용하기 쉽습니다.
{
"version": "1.4.0",
"locales": {
"ko-KR": {
"title": "1.4.0 업데이트",
"sections": {
"features": [],
"bug_fixes": ["특정 협동 전투에서 보스 보호막의 피해 감소 효과가 중복 적용되던 문제를 수정했습니다."],
"balance": [],
"content": [],
"art_audio": [],
"improvements": []
}
},
"en-US": {
"title": "Update 1.4.0",
"sections": {
"features": [],
"bug_fixes": ["Fixed an issue where the boss shield's damage reduction effect was applied twice in certain co-op battles."],
"balance": [],
"content": [],
"art_audio": [],
"improvements": []
}
}
},
"review_flags": []
}
용어집과 번역 메모를 왜 분리해야 하는가?
게임 고유명사는 일반적인 기계 번역보다 프로젝트 용어집이 우선되어야 합니다. 캐릭터명, 스킬명, 아이템명, 지역명, 플랫폼별 표기 규칙을 별도 파일로 관리합니다.
terms:
"황혼의 숲":
en-US: "Twilight Forest"
ja-JP: "黄昏の森"
"빛의 파편":
en-US: "Light Shard"
ja-JP: "光の欠片"
"보호막":
en-US: "shield"
ja-JP: "シールド"
style:
ko-KR: "존댓말, 간결한 패치노트 문장"
en-US: "Clear, concise player-facing English"
ja-JP: "自然なゲーム 공지체, 공식 용어 우선"
용어집은 번역 품질뿐 아니라 제품 운영의 일관성을 지키는 기준입니다. 새 콘텐츠가 추가될 때 용어집 변경을 별도의 Pull Request로 검토하도록 운영하면 나중에 대규모 재번역을 줄일 수 있습니다.
GitHub Actions에서 릴리즈 노트 생성을 자동화하는 방법
자동화 시점은 크게 두 가지가 있습니다.
| 실행 시점 | 장점 | 주의점 |
|---|---|---|
| Pull Request 생성 또는 갱신 | 릴리즈 전에 미리 검수 가능 | 아직 최종 병합되지 않은 변경이 포함될 수 있음 |
| Git 태그 생성 | 실제 배포 범위와 일치 | 릴리즈 직전 수정 시간이 부족할 수 있음 |
| 수동 workflow dispatch | 운영 담당자가 원하는 시점에 실행 | 실행 누락 가능성 존재 |
권장 방식은 Pull Request에서 초안을 만들고 Git 태그 생성 시 최종본을 확정하는 2단계 운영입니다. 아래 YAML은 태그를 직접 입력하는 workflow_dispatch 예시입니다. Pull Request나 태그 푸시로 자동 실행하려면 해당 이벤트를 별도로 설정하고, 이전·현재 태그를 정하는 로직도 워크플로에 추가해야 합니다.
name: Generate Release Notes
on:
workflow_dispatch:
inputs:
previous_tag:
description: "Previous release tag"
required: true
current_tag:
description: "Current release tag"
required: true
permissions:
contents: read
jobs:
generate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Collect commits
run: python scripts/collect_release_commits.py
env:
PREVIOUS_TAG: ${{ inputs.previous_tag }}
CURRENT_TAG: ${{ inputs.current_tag }}
- name: Generate draft
run: python scripts/generate_localized_notes.py
env:
RELEASE_VERSION: ${{ inputs.current_tag }}
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
- name: Upload draft
uses: actions/upload-artifact@v4
with:
name: localized-release-notes
path: release-notes/
실제 운영에서는 번역 API 키를 GitHub Actions Secret에 저장하고 생성 결과를 곧바로 공개하지 않습니다. 생성 파일은 Artifact로 보관하거나 Pull Request에 커밋해 담당자가 검수한 뒤 배포합니다.
자동 생성 결과를 어떤 기준으로 검수해야 할까?
자동화 품질은 생성 모델의 문장력보다 검수 기준의 명확성에 더 크게 좌우됩니다. 다음 체크리스트를 릴리즈 승인 조건으로 사용하면 좋습니다.
- 입력 커밋 범위가 이전 태그와 현재 태그 사이에 정확히 포함되었는지 확인합니다.
Release-Note: internal항목과 비공개 기능 플래그가 결과에서 제외되었는지 확인합니다.- 수치, 아이템명, 스킬명, 플랫폼 용어가 원문과 일치하는지 확인합니다.
- 버그 수정 문장이 플레이어에게 어떤 변화가 있는지 설명하는지 확인합니다.
- 한국어·영어·일본어 문서의 항목 수와 섹션 순서가 동일한지 비교합니다.
- 커밋 해시, 내부 프로젝트명, 고객 정보, 서버 주소가 노출되지 않았는지 확인합니다.
- 긴급 수정이나 알려진 문제처럼 별도 안내가 필요한 항목이 빠지지 않았는지 확인합니다.
- 최종 승인자가 문서 버전을 릴리즈 태그와 연결했는지 확인합니다.
특히 숫자 검증은 자동화하기 쉽습니다. 원문에 포함된 숫자 집합과 번역 결과의 숫자 집합을 비교해 불일치가 있으면 review_flags에 추가할 수 있습니다.
import re
def numbers(text: str) -> set[str]:
return set(re.findall(r"\d+(?:\.\d+)?%?", text))
def has_number_mismatch(source: str, translated: str) -> bool:
return numbers(source) != numbers(translated)
이 검사는 번역 문장의 자연스러움을 판단하지는 못하지만 피해량, 재사용 대기시간, 보상 수량 같은 핵심 수치가 바뀌는 사고를 빠르게 발견하는 데 유용합니다.
릴리즈 노트 자동화에서 자주 발생하는 실패는 무엇인가?
커밋 제목이 너무 기술적인 경우
fix(entity): null reference guard 같은 문장은 개발자에게는 충분하지만 플레이어에게는 의미가 분명하지 않습니다. 커밋 본문에 플레이어 영향 필드를 추가하고 생성 단계에서 해당 내용을 우선 사용하도록 합니다.
하나의 커밋에 여러 변경 사항을 넣는 경우
feat: 전투, 상점, 퀘스트 수정처럼 여러 영역을 하나의 커밋에 넣으면 자동 분류가 흔들립니다. 하나의 사용자 영향 단위마다 커밋을 나누거나 본문에 명시적인 변경 목록을 작성해야 합니다.
번역 결과가 언어별로 달라지는 경우
언어마다 항목을 독립적으로 생성하면 한 언어에는 포함된 버그 수정이 다른 언어에서는 빠질 수 있습니다. 먼저 언어 중립적인 변경 데이터와 섹션 구조를 만들고 그 구조를 모든 번역에 재사용해야 합니다.
생성 결과를 즉시 공개하는 경우
자동 생성은 초안 단계에서 사용해야 합니다. 공개 문서에 반영하기 전 담당자 승인과 민감 정보 검사를 거치고 문제가 있는 항목은 커밋 메시지 또는 릴리즈 데이터에서 수정합니다.
단계별 도입 계획
1단계: 커밋 규칙과 공개 여부부터 정한다
팀 문서에 허용된 타입과 스코프를 정의하고 Commitlint 또는 GitHub Pull Request 검사로 형식을 검증합니다. 동시에 public, internal, omit처럼 릴리즈 노트 포함 여부를 표시하는 규칙을 정합니다.
2단계: 한 언어의 템플릿부터 자동화한다
처음부터 모든 플랫폼과 언어를 연결하지 말고 한국어 Markdown 초안 생성을 먼저 안정화합니다. 이 단계에서는 커밋 범위, 분류, 제외 규칙, 중복 제거가 제대로 작동하는지 확인합니다.
3단계: 용어집과 다국어 출력을 추가한다
한국어 초안이 승인되면 동일한 구조를 영어와 일본어로 변환합니다. 번역 결과에는 원문 커밋과 생성 시각, 사용한 용어집 버전을 함께 기록해 재현성을 확보합니다.
4단계: 릴리즈 파이프라인에 연결한다
GitHub Actions에서 태그를 감지하고 초안을 생성한 뒤 승인된 결과만 스토어 공지나 공식 웹사이트에 배포합니다. 실패한 번역 작업은 전체 릴리즈를 막을지 또는 해당 언어만 보류할지 팀 운영 정책으로 정해야 합니다.
비용과 운영 범위는 어떻게 관리할까?
모든 커밋을 매번 번역하면 비용과 처리 시간이 늘어납니다. 다음 최적화가 효과적입니다.
docs,chore,infra처럼 공개 가치가 낮은 타입은 입력 단계에서 제외합니다.- 동일한 커밋 해시와 용어집 버전 조합은 결과를 캐시합니다.
- 긴 커밋 본문 전체보다
subject,플레이어 영향,QA필드만 모델에 전달합니다. - 긴급 핫픽스는 전체 재생성 대신 변경된 섹션만 다시 생성합니다.
- 언어별 출력보다 먼저 공통 JSON 구조를 확정해 중복 처리를 줄입니다.
릴리즈 노트 생성 비용은 다음과 같이 단순하게 추정할 수 있습니다.
자동화의 효과는 생성 비용만 줄이는 데 있지 않습니다. 반복적인 수집과 서식 정리에 쓰이던 C_{collect}와 C_{publish}를 줄이고 검수자가 중요한 내용에 집중하도록 만드는 데 있습니다.
자주 묻는 질문 (FAQ)
커밋 메시지가 Conventional Commits 형식이 아니면 어떻게 하나요?
정규식으로 분류할 수 없는 커밋은 자동 생성에서 제외하고 review_flags에 기록하는 방식이 안전합니다. 누락을 숨기기보다 검수 대상이라고 표시해야 합니다.
번역 모델이 사실과 다른 내용을 만들면 어떻게 막나요?
입력에 없는 정보 추가 금지, 수치 보존, 용어집 우선, JSON Schema 출력 규칙을 프롬프트에 포함하고 최종 공개 전 담당자 승인을 둡니다. 숫자 비교와 금칙어 검사를 자동화하면 위험을 더 줄일 수 있습니다.
모든 커밋을 플레이어에게 공개해야 하나요?
아닙니다. 플레이어에게 영향을 주는 feat, fix, balance, content, art, perf 중심으로 공개하고 내부 리팩터링이나 빌드 설정 변경은 제외하는 것이 일반적입니다.
정리
Git 커밋 로그 기반 릴리즈 노트 자동화는 번역 도구 하나를 붙이는 작업이 아니라 팀의 변경 기록을 표준화하는 운영 프로젝트입니다. Conventional Commits 1.0.0으로 입력을 정리하고 Python 3.12로 릴리즈 범위를 추출한 다음 GitHub Actions에서 공통 JSON 구조와 용어집을 사용해 다국어 초안을 생성하면 재현성과 검수 가능성을 함께 확보할 수 있습니다.
가장 현실적인 도입 순서는 한국어 초안 자동화 → 검수 기준 정립 → 용어집 추가 → 다국어 배포 연결입니다. 자동 생성 결과를 최종 판단이 아닌 검수 가능한 초안으로 운영할 때 릴리즈 일정과 문서 품질을 모두 안정적으로 관리할 수 있습니다.



