From 2bb55d51a52d95cd92861d3ed4b24ae239963e33 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:56:14 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[docs][backlog]=20README=20=EC=9E=AC?= =?UTF-8?q?=EA=B5=AC=EC=84=B1=20=EB=93=9C=EB=9E=98=ED=94=84=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README가 구현 서술 중심이라 "그래서 커밋이 어떤 모양이 되는가"에 도달하려면 훅 설명을 먼저 통과해야 한다. 최종 산출물 중심으로 재배치하고 각 필드의 의미와 생성 시점, 이유를 표로 정리한다. GF-135에서 거짓 서술을 걷어내며 오히려 늘린 분량도 함께 압축한다. 아직 태스크가 아니다 - 승격은 별도 커밋으로 남긴다. Task-Id: GF-136 Co-Authored-By: Claude Opus 5 AI-Tool: claude-code AI-Tool-Version: 2.1.267 Co-Authored-By: Claude AI-Model: claude-opus-5 Tokens-Used: 10259703 Tool-Calls: 8 Hooks-Commit: fad91f7 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...54\354\204\261\355\225\234\353\213\244.md" | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 "backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" diff --git "a/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" "b/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" new file mode 100644 index 0000000..3c02b38 --- /dev/null +++ "b/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" @@ -0,0 +1,49 @@ +--- +id: DRAFT-18 +title: README를 최종 커밋 포맷 중심으로 재구성한다 +status: Draft +assignee: [] +created_date: '2026-09-26 07:55' +updated_date: '2026-09-26 07:56' +labels: + - docs +dependencies: [] +documentation: + - backlog/docs/doc-13 - git-format-재설계-계획-—-커밋-규칙을-prepare-commit-msg로-통합.md +modified_files: + - README.md +priority: medium +type: docs +--- + +## Description + + +README가 구현 서술 중심이다 — "왜 만들었는지 / 무엇을 만들었는지 / 훅을 생애주기별로" 순서라, 읽는 사람이 정작 알고 싶은 "그래서 커밋이 어떤 모양이 되는가"에 도달하기까지 훅 설명을 먼저 통과해야 한다. + +유저 지시: 최종적으로 완성되는 포맷을 보여주고, 각 필드의 의미를 표로 정리하고, 언제 만들어지는지와 왜 만들어지는지를 함께 정리할 것. + +또 GF-135에서 거짓 서술을 걷어내면서 오히려 분량을 늘렸다(언어 lint 범위 설명 두 문단, 컨슈머 한계 인용 블록, prepare-commit-msg 중첩 불릿, 재설계 진행 알림 블록). 재구성하면서 함께 압축한다 — 새 설명을 보태지 않고 기존 내용을 재배치한다. + +주의: 현재 구조는 재설계 중이다. 트레일러 삽입은 아직 post-commit이 하고 메시지 검증은 아직 commit-msg가 한다. '언제 만들어지는지' 열은 지금 사실대로 쓰고, GF-127/GF-128이 그 시점을 옮기면 갱신해야 한다. hooks/README.md와 최종 아키텍처 반영은 GF-134가 계속 맡는다. + + +## Acceptance Criteria + +- [ ] #1 최종적으로 완성되는 커밋 메시지 전문을 문서 앞부분에 제시한다 — 제목·본문·footer가 모두 채워진 실제 예시 하나 +- [ ] #2 각 필드를 표로 정리한다: 제목 구성요소(type, subsystem, 설명)와 트레일러 전체 +- [ ] #3 표에 각 필드의 의미, 언제(어느 훅의 어느 시점) 만들어지는지, 왜 필요한지를 함께 담는다 +- [ ] #4 사람이 직접 쓰는 필드와 훅이 자동으로 붙이는 필드를 구분해 표시한다 +- [ ] #5 값의 신뢰 수준(강제/서버 발급/자가신고/자동)을 표에 유지한다 — 기존 트레일러 표의 핵심 정보다 +- [ ] #6 GF-135가 늘린 분량을 압축한다: 언어 lint 범위 설명, 컨슈머 한계 인용 블록, prepare-commit-msg 중첩 불릿, 재설계 진행 알림 블록 +- [ ] #7 새 설명을 보태지 않는다 — 기존 내용의 재배치와 압축만 한다 +- [ ] #8 '언제 만들어지는지'는 현재 사실대로 쓴다 (트레일러 삽입은 post-commit, 메시지 검증은 commit-msg). GF-127/128이 옮기면 갱신해야 한다는 점을 태스크 노트에 남긴다 +- [ ] #9 README 순증 줄 수가 0 이하다 (GF-135 이전 대비) + + +## Definition of Done + +- [ ] #1 python3 -m unittest 스위트 전체 통과 +- [ ] #2 python3 -m unittest 스위트 전체 통과 +- [ ] #3 문서에 적힌 커밋 예시가 실제로 생성되는 footer와 일치하는지 확인 + From 75ce5daf21e93f4d92373d67102d637de0b85834 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:57:06 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[docs][backlog]=20README=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=ED=94=84=ED=8A=B8=EC=97=90=20=ED=9B=85=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=94=84=EC=82=AC=EC=9D=B4=ED=81=B4=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 필드 표에 그 필드를 만드는 훅 이름을 적고, 커밋 한 번에 훅이 도는 순서와 각 단계에서 커밋 객체가 존재하는지를 함께 정리한다. --no-verify가 어느 훅을 건너뛰는지도 표시한다 - 어떤 필드가 우회 가능한지가 거기서 갈린다. Task-Id: GF-136 Co-Authored-By: Claude Opus 5 AI-Tool: claude-code AI-Tool-Version: 2.1.267 Co-Authored-By: Claude AI-Model: claude-opus-5 Tokens-Used: 6488496 Tool-Calls: 4 Hooks-Commit: 8848a5d Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...54\352\265\254\354\204\261\355\225\234\353\213\244.md" | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git "a/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" "b/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" index 3c02b38..2ec7f8d 100644 --- "a/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" +++ "b/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" @@ -4,7 +4,7 @@ title: README를 최종 커밋 포맷 중심으로 재구성한다 status: Draft assignee: [] created_date: '2026-09-26 07:55' -updated_date: '2026-09-26 07:56' +updated_date: '2026-09-26 07:57' labels: - docs dependencies: [] @@ -39,11 +39,13 @@ README가 구현 서술 중심이다 — "왜 만들었는지 / 무엇을 만들 - [ ] #7 새 설명을 보태지 않는다 — 기존 내용의 재배치와 압축만 한다 - [ ] #8 '언제 만들어지는지'는 현재 사실대로 쓴다 (트레일러 삽입은 post-commit, 메시지 검증은 commit-msg). GF-127/128이 옮기면 갱신해야 한다는 점을 태스크 노트에 남긴다 - [ ] #9 README 순증 줄 수가 0 이하다 (GF-135 이전 대비) +- [ ] #10 필드 표에 그 필드를 만드는 git 훅 이름을 명시한다 (prepare-commit-msg / commit-msg / post-commit 중 어느 것인지) +- [ ] #11 git 커밋 훅 라이프사이클을 문서에 함께 정리한다 — 커밋 한 번에 훅이 도는 순서와 각 단계에서 커밋 객체가 존재하는지 여부 +- [ ] #12 라이프사이클에서 --no-verify가 어느 훅을 건너뛰는지 표시한다 — 어떤 필드가 우회 가능하고 어떤 필드가 불가능한지가 여기서 갈린다 ## Definition of Done - [ ] #1 python3 -m unittest 스위트 전체 통과 -- [ ] #2 python3 -m unittest 스위트 전체 통과 -- [ ] #3 문서에 적힌 커밋 예시가 실제로 생성되는 footer와 일치하는지 확인 +- [ ] #2 문서에 적힌 커밋 예시가 실제로 생성되는 footer와 일치하는지 확인 From ba899858baa527a87f6f8d5dc5fb60bfabbeb23b Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:57:26 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[docs][backlog]=20DRAFT-18=EC=9D=84=20GF-13?= =?UTF-8?q?6=EC=9C=BC=EB=A1=9C=20=EC=8A=B9=EA=B2=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-136 Co-Authored-By: Claude Opus 5 AI-Tool: claude-code AI-Tool-Version: 2.1.267 Co-Authored-By: Claude AI-Model: claude-opus-5 Tokens-Used: 1952818 Tool-Calls: 2 Hooks-Commit: c828491 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...36\254\352\265\254\354\204\261\355\225\234\353\213\244.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename "backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" => "backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" (99%) diff --git "a/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" "b/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" similarity index 99% rename from "backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" rename to "backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" index 2ec7f8d..e235317 100644 --- "a/backlog/drafts/draft-18 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" @@ -1,7 +1,7 @@ --- -id: DRAFT-18 +id: GF-136 title: README를 최종 커밋 포맷 중심으로 재구성한다 -status: Draft +status: To Do assignee: [] created_date: '2026-09-26 07:55' updated_date: '2026-09-26 07:57' From 5f4c5c01bc7f08ac4edd3f2be3eaa3591e02cd5c Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:01:20 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[docs]=20README=EB=A5=BC=20=EC=B5=9C?= =?UTF-8?q?=EC=A2=85=20=EC=BB=A4=EB=B0=8B=20=ED=8F=AC=EB=A7=B7=20=EC=A4=91?= =?UTF-8?q?=EC=8B=AC=EC=9C=BC=EB=A1=9C=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구현 서술 순서(왜 만들었는지 → 무엇을 만들었는지 → 훅 생애주기)라 "그래서 커밋이 어떤 모양이 되는가"에 도달하려면 훅 설명을 먼저 통과해야 했다. 완성된 커밋 예시를 맨 앞에 두고 각 필드의 의미, 만드는 훅, 신뢰 수준을 표로 정리했다. 훅 라이프사이클 표는 28행 중 커밋 경로만 남기고, 커밋 객체가 존재하는지와 --no-verify가 어느 훅을 건너뛰는지를 열로 넣었다. 에디터가 훅보다 뒤에 열린다는 사실과 post-commit이 커밋을 막을 수 없다는 사실이 그 표에서 바로 읽힌다. 203줄에서 163줄로 줄었다. 예시는 현재 실제로 생성되는 footer 그대로다 - AI-Agent 병합은 GF-128 예정이라 아직 세 트레일러로 나뉘어 있다. Task-Id: GF-136 Co-Authored-By: Claude Opus 5 AI-Tool: claude-code AI-Tool-Version: 2.1.267 Co-Authored-By: Claude AI-Model: claude-opus-5 Tokens-Used: 15172793 Tool-Calls: 12 Hooks-Commit: 935817e Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- README.md | 205 +++++++++++++++++++++--------------------------------- 1 file changed, 79 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 8fbde0c..1136947 100644 --- a/README.md +++ b/README.md @@ -22,136 +22,89 @@ --- -## 🤔 왜 만들었는지 말씀드립니다 +## 📋 완성되는 커밋은 이런 모양입니다 -여러 저장소에서 커밋 규칙이 제각각이거나 아예 없었고, `git commit --no-verify`로 -검사를 우회해도 아무 흔적이 남지 않는 문제가 있었습니다. 여기에 더해, 사람이 아니라 AI -코딩 에이전트(특히 Claude Code)가 커밋을 만드는 경우가 늘면서 이 문제가 결정적으로 -중요해졌습니다. - -**이 git 커밋 형식을 강제하는 궁극적인 이유는 다음 두 가지라고 말씀드릴 수 있습니다.** - -1. AI 에이전트가 수많은 커밋을 남겼을 때, 나중에 그 작업 이력을 분석할 수 있게 - 만드는 것입니다. -2. 그렇게 남은 기록을 여전히 사람이 이해할 수 있는지 검증하는 것입니다. - -형식이 일관되면 사람도 LLM도 `git log` 한 번으로 "무엇을, 왜, 어떻게 검증하고 -바꿨는지"를 바로 읽어낼 수 있습니다. 커밋별 토큰 소비량(`Tokens-Used`/`Tool-Calls`)을 -남기는 것도 같은 목적의 연장선입니다 — 다만 이 측정 방법론은 아직 실험 단계라는 점을 -말씀드립니다(자세한 내용은 아래 [훅 생애주기](#-훅을-생애주기별로-정리해-드립니다)를 -참고해 주시기 바랍니다). - -## 🛠️ 무엇을 만들었는지 말씀드립니다 - -Python 훅 + 설정 파일 하나(`hooks/gitformat.conf`) + POSIX sh 설치 -스크립트(`install.sh`)로 구성된, **git 자체 기능만으로 동작하는** 도구를 만들었습니다. -`core.hooksPath` · `init.templateDir` · `git interpret-trailers` 같은 git 내장 -메커니즘만 쓰고, 훅은 Python 3 표준 라이브러리만 씁니다(decision-16) — pip/npm으로 -설치할 패키지는 없지만 `python3` 자체는 필요합니다([필요조건](#-필요조건을-말씀드립니다)). - -**다루는 범위는 커밋 메시지 형식과 트레일러입니다.** 언어별 lint는 다루지 않습니다 -(decision-23) — 커밋 형식을 맞추는 도구가 언어 도구를 돌릴 이유가 없고, 그 결합이 -컨슈머에게 npm/ruff/clang-format/mvn/sqlfluff의 존재를 전제하게 만들었기 때문입니다. - -> ⚠️ **알려진 한계**: git-format은 `core.hooksPath`를 점유하므로 그 저장소의 -> `.git/hooks/*`는 무시됩니다. 언어 검사가 필요하면 CI에서 돌리시기 바랍니다 — -> git-format과 자기 훅을 함께 쓰는 지원 방법은 아직 정하지 않았습니다. +``` +[docs][backlog] DRAFT-18을 GF-136으로 승격 ← 사람(또는 에이전트)이 씀 + ← 빈 줄 (규칙) +왜 바꿨는지 설명하는 본문. 한 줄 72자 이내. ← 선택, 사람이 씀 + ← 빈 줄 +Task-Id: GF-136 ↓ 아래는 전부 훅이 자동으로 붙임 +AI-Tool: claude-code +AI-Tool-Version: 2.1.267 +Co-Authored-By: Claude +AI-Model: claude-opus-5 +Tokens-Used: 1952818 +Tool-Calls: 2 +Hooks-Commit: c828491 +Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> +``` -## 🪝 훅을 생애주기별로 정리해 드립니다 +`git log` 한 번으로 "무엇을, 왜, 누가(사람인지 어떤 모델인지), 얼마를 써서 바꿨는지"가 +읽히게 하는 것이 목표입니다. AI 에이전트가 커밋을 대량으로 남길 때 그 이력을 나중에 +분석할 수 있어야 하고, 동시에 사람이 읽어도 이해되어야 합니다. -> 공식 문서: [githooks(5)](https://git-scm.com/docs/githooks) +## 🧩 각 필드가 무엇이고 언제 만들어지는지 -git이 공식적으로 제공하는 훅은 총 28개입니다. 이 중 git-format이 실제로 구현해 -연결한 것은 커밋 단계의 3개(`pre-commit`/`commit-msg`/`post-commit`)뿐이라는 점을 -표로 정리해 드립니다. 훅 이름을 누르시면 실제 소스 파일로 이동합니다. +**제목과 본문** — 사람(또는 에이전트)이 직접 씁니다. `commit-msg`가 검증합니다. -| 훅 | 실행 시점 | git-format 연결 | +| 필드 | 무엇인가 · 왜 필요한가 | 규칙 | | --- | --- | --- | -| `applypatch-msg` | `git am` 패치 적용 전 커밋 메시지 검증/수정 | — | -| `pre-applypatch` | `git am` 패치 적용 후 커밋 전 작업 트리 검사 | — | -| `post-applypatch` | `git am` 패치 적용·커밋 완료 후 알림 | — | -| **`pre-commit`** | `git commit` 커밋 전 코드 검사 | [`hooks/pre-commit`](hooks/pre-commit) | -| `pre-merge-commit` | `git merge` 완료 후 커밋 메시지 입력 전 | — | -| `prepare-commit-msg` | 기본 커밋 메시지 준비 후 에디터 시작 전 | — | -| **`commit-msg`** | 커밋 메시지 형식 검증/수정 | [`hooks/commit-msg`](hooks/commit-msg) | -| **`post-commit`** | 커밋 완료 후 알림(git이 항상 실행을 보장) | [`hooks/post-commit`](hooks/post-commit) | -| `pre-rebase` | `git rebase` 전, 특정 브랜치 리베이스 방지 | — | -| `post-checkout` | `git checkout`/`switch` 후 작업 트리 업데이트 후 | — | -| `post-merge` | `git merge`/`pull` 완료 후 | — | -| `pre-push` | `git push` 전, 푸시 거부 가능 | — | -| `pre-receive` | (서버측) 참조 업데이트 시작 전 | — | -| `update` | (서버측) 참조별 업데이트 전, 강제 푸시 방지 | — | -| `proc-receive` | (서버측) 특정 참조 업데이트 처리 | — | -| `post-receive` | (서버측) 모든 참조 업데이트 완료 후, 알림/배포 | — | -| `post-update` | (서버측) 모든 참조 업데이트 후, HTTP 정보 갱신 | — | -| `reference-transaction` | 참조 업데이트 트랜잭션 모니터링 | — | -| `push-to-checkout` | push가 현재 체크아웃 브랜치를 업데이트할 때 | — | -| `pre-auto-gc` | `git gc --auto` 전 | — | -| `post-rewrite` | `commit --amend`/`rebase` 등 커밋 재작성 후 | — | -| `sendemail-validate` | `git send-email` 발송 전 패치 검증 | — | -| `fsmonitor-watchman` | watchman 연동 파일 변경 모니터링 | — | -| `p4-pre-submit` | `git-p4 submit` 전 | — | -| `p4-prepare-changelist` | p4 기본 체인지리스트 준비 후 | — | -| `p4-changelist` | p4 체인지리스트 메시지 편집 후 | — | -| `p4-post-changelist` | p4 제출 완료 후 | — | -| `post-index-change` | 인덱스 write 시 | — | - -git-format은 push 단계(`pre-push` 이후)와 서버측 훅은 다루지 않습니다 — 커밋 -단계까지만 다룬다는 원칙(decision-11, decision-12) 때문입니다. - -`git commit`을 실행하면 연결된 훅이 아래 순서로 개입한다는 점을 이어서 -말씀드립니다. - -1. **[`prepare-commit-msg`](hooks/prepare-commit-msg)** — 커밋 객체가 만들어지기 - **전에** 돌고, `--no-verify`로도 건너뛸 수 없습니다(실측 근거는 doc-15). 그래서 - 커밋 규칙 강제가 이 훅으로 모입니다(decision-18). 지금 하는 일은 재생·병합 커밋 - 면제, 에디터 경로 거부, 검증 마커 기록입니다. - - **재생·병합 커밋 면제**: cherry-pick/rebase/revert/merge로 만들어지는 커밋은 - 이미 검증된 커밋의 복제이므로 건드리지 않습니다. - - **에디터 경로 거부**: 에디터는 이 훅보다 **뒤에** 열려서 사람이 타이핑한 최종 - 메시지를 훅이 볼 수 없습니다. 그래서 `git commit -m`(또는 `-F`)만 허용합니다 — - 이렇게 하면 "통과한 커밋은 모두 검증을 거쳤다"가 성립합니다. -2. **[`commit-msg`](hooks/commit-msg)** — 커밋 메시지가 `[type][subsystem] ` 형식인지, 제목 - 50자/본문 줄 72자 이내인지, 본문이 있으면 빈 줄이 있는지, 브랜치명에 - Task-Id(`GF-<번호>`, decision-4)가 있는지, (non-Claude-Code AI 도구라면) `AI-Model`이 - 화이트리스트에 있는지 검증합니다. 여기서 거부되면 커밋 자체가 만들어지지 않습니다. -3. **(커밋 생성)** — 통과하면 git이 실제로 커밋을 만듭니다. -4. **[`post-commit`](hooks/post-commit)** — `--no-verify`로도 건너뛸 수 없고, exit code가 - 커밋 결과에 영향을 주지도 못한다는 점을 [공식 문서](https://git-scm.com/docs/githooks)로도 - 확인하실 수 있습니다. 검증 마커가 없으면(= 앞 훅이 돌지 않은 경우) - `Verify-Bypassed: true`를 `git commit --amend`로 삽입하고(decision-3), `Task-Id`, - `Signed-off-by`(decision-10), `Hooks-Commit`과, AI 에이전트가 커밋했다면 아래 AI - 귀속/토큰 트레일러를 함께 붙입니다(decision-5). - -> 이 구조는 재설계 중입니다(decision-18). 검증과 트레일러 삽입을 -> `prepare-commit-msg`로 옮기는 중이고, `pre-commit`은 이미 삭제됐습니다. - -| 트레일러 | 값 | 신뢰 수준 | -| ----------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| `AI-Tool`, `AI-Tool-Version` | `AI_AGENT` 환경변수(Claude Code가 하위 프로세스에 주입) | 강제 — LLM이 스스로 만든 값이 아님 | -| `AI-Model` | Claude Code: 세션 트랜스크립트의 `message.model`(서버 발급) / 그 외 도구: `gitformat.aiModel` 설정값 | Claude Code는 서버 발급 사실 / 그 외는 자가신고 | -| `Tokens-Used` | 이전 커밋 이후 세션에서 소비된 토큰 델타(누적 아님) — Claude Code 전용, 실패 시 `unavailable (사유)` | 서버 발급 사실(Claude Code 한정) / 그 외는 실측 채널 없음 | -| `Tool-Calls` | 같은 구간의 `tool_use` 콘텐츠 블록 개수 | 위와 동일 | -| `Co-Authored-By` | `AI-Tool`이 `claude-code`일 때만 자동 삽입 | 자동 | -| `Hooks-Commit` | 이 git-format 클론 자체의 `git rev-parse --short HEAD` | 완전 자동, 모든 커밋에 적용 | -| `Signed-off-by` | 커미터 정보(`git log -1 --format='%cn <%ce>'`) | 완전 자동, 모든 커밋에 적용 | - -`Tokens-Used`/`Tool-Calls` 측정에 실패하면 사유 슬러그와 함께 `unavailable (사유)`로 -기록된다는 점을 말씀드립니다 — 사유 슬러그: `no-session-id`, `transcript-not-found`, -`transcript-unreadable`, `transcript-parse-failed`, `no-usage-channel`입니다. - -예시를 보여드리면 다음과 같습니다. - -``` -[fix][login] 빈 비밀번호 입력 시 크래시 수정 - -Task-Id: GF-42 -Signed-off-by: Jane Dev -Hooks-Commit: b5bf03a -``` - -git-format은 커밋 단계까지만 다룬다는 점을 말씀드립니다 — `git push`는 아무 훅도 -거치지 않는 평범한 push입니다(decision-12). +| `type` | 변경의 종류. `git log`를 종류별로 걸러 읽을 수 있게 합니다 | `feat fix docs style refactor perf test build ci chore revert` 중 하나 | +| `subsystem` | 영향 범위. 어느 부분이 바뀌었는지 제목만 보고 알게 합니다 | 선택. `[a-zA-Z0-9_.-]` | +| 설명 | 무엇을 했는지 | **50자 이내**(유니코드 코드포인트), 명령형 현재형 | +| 본문 | **왜** 바꿨는지. 어떻게는 diff가 이미 보여줍니다 | 선택. 한 줄 **72자 이내**, 제목과 빈 줄로 분리 | + +**트레일러** — 훅이 자동으로 붙입니다. 사람이 타이핑하지 않습니다. + +| 트레일러 | 무엇인가 · 왜 필요한가 | 만드는 훅 · 값의 출처 | 신뢰 수준 | +| --- | --- | --- | --- | +| `Task-Id` | 이 커밋이 어느 작업의 일부인지. 커밋과 태스크를 잇습니다 | `post-commit` · 브랜치명의 `-<번호>`(decision-4) | 자동 | +| `AI-Tool`
`AI-Tool-Version` | 어떤 도구가 커밋을 만들었는지 | `post-commit` · `AI_AGENT` 환경변수(Claude Code가 하위 프로세스에 주입) | 강제 — LLM이 스스로 만든 값이 아님 | +| `AI-Model` | 어떤 모델이 썼는지. 모델별 작업 품질을 나중에 비교할 수 있게 합니다 | `post-commit` · Claude Code는 세션 트랜스크립트의 `message.model`, 그 외는 `gitformat.aiModel` 설정값 | Claude Code는 서버 발급 사실 / 그 외는 자가신고 | +| `Tokens-Used` | 이 커밋에 든 토큰. 작업 비용을 이력에서 읽게 합니다 | `post-commit` · 직전 커밋 이후 세션 구간의 델타(누적 아님) | 서버 발급 사실(Claude Code 한정), **측정 방법론은 실험 단계** | +| `Tool-Calls` | 같은 구간의 도구 호출 수 | `post-commit` · `tool_use` 블록 개수 | 위와 동일 | +| `Co-Authored-By` | 공동저자 귀속 | `post-commit` · `AI-Tool`이 `claude-code`일 때만 | 자동 | +| `Hooks-Commit` | 이 커밋을 검사한 git-format 자체의 버전. 훅에 버그가 있었을 때 어느 커밋들이 그 훅을 거쳤는지 역추적합니다 | `post-commit` · 훅 클론의 `rev-parse --short HEAD` | 완전 자동, 모든 커밋 | +| `Signed-off-by` | 커미터 정보(DCO 관례) | `post-commit` · `git log -1 --format='%cn <%ce>'` | 완전 자동, 모든 커밋 | +| `Verify-Bypassed` | `--no-verify`로 검사를 건너뛴 사실. 우회를 막지는 못하니 대신 기록합니다(decision-3) | `post-commit` · `prepare-commit-msg`가 남긴 검증 마커의 **부재** | 자동 | +| `Fixes` | 이 버그를 만든 커밋 | **사람이 씀**(원인 커밋을 아는 경우만) · `commit-msg`가 해시 실재를 검증 | 검증됨 | + +`Tokens-Used`/`Tool-Calls`는 측정에 실패하면 사유와 함께 `unavailable (사유)`로 남습니다 — +`no-session-id`, `transcript-not-found`, `transcript-unreadable`, `transcript-parse-failed`, +`no-usage-channel`. + +## 🪝 커밋 한 번에 훅이 도는 순서 + +> 공식 문서: [githooks(5)](https://git-scm.com/docs/githooks). git이 제공하는 훅은 28개지만 +> git-format은 커밋 단계만 다룹니다(decision-11, decision-12) — `git push`는 아무 훅도 +> 거치지 않는 평범한 push입니다. + +| 순서 | 훅 | 커밋 객체가 있는가 | `--no-verify`로 건너뛰나 | git-format이 하는 일 | +| --- | --- | --- | --- | --- | +| 1 | [`prepare-commit-msg`](hooks/prepare-commit-msg) | 아직 없음 | **건너뛸 수 없음** | 재생·병합 커밋 면제, 에디터 경로 거부, 검증 마커 기록 | +| 2 | (에디터) | 아직 없음 | — | 사람이 메시지를 씁니다. 훅보다 **뒤**라서 1번은 최종 메시지를 볼 수 없습니다 | +| 3 | [`commit-msg`](hooks/commit-msg) | 아직 없음 | 건너뜀 | 제목 형식·길이, 빈 줄, `Fixes` 해시, 브랜치 `Task-Id`, `AI-Model` 화이트리스트 검증 | +| 4 | (커밋 생성) | **생성됨** | — | git이 커밋 객체를 만듭니다 | +| 5 | [`post-commit`](hooks/post-commit) | 있음 | 건너뛸 수 없음 | 트레일러를 `git commit --amend`로 삽입. exit code가 커밋 결과에 영향을 주지 못합니다 | + +여기서 두 가지가 나옵니다. **에디터가 1번보다 뒤에 열리므로** 최종 메시지 검증은 +`git commit -m`(또는 `-F`)만 가능하고, 그래서 에디터 경로는 거부합니다. +그리고 **4번에서 커밋이 이미 만들어지므로** `post-commit`은 커밋을 막을 수 없습니다 — +검사가 아니라 기록만 합니다. + +## 🎯 다루는 범위 + +커밋 메시지 형식과 트레일러입니다. **언어별 lint는 다루지 않습니다**(decision-23) — +커밋 형식을 맞추는 도구가 언어 도구를 돌릴 이유가 없고, 그 결합이 컨슈머에게 +npm/ruff/clang-format/mvn/sqlfluff의 존재를 전제하게 만들었습니다. 언어 검사가 필요하면 +CI에서 돌리시기 바랍니다 — git-format이 `core.hooksPath`를 점유하므로 그 저장소의 +`.git/hooks/*`는 무시되고, 자기 훅과 함께 쓰는 방법은 아직 정하지 않았습니다. + +구성은 Python 훅 + 설정 파일 하나(`hooks/gitformat.conf`) + POSIX sh 설치 +스크립트(`install.sh`)입니다. `core.hooksPath` · `init.templateDir` · +`git interpret-trailers` 같은 git 내장 메커니즘만 쓰고, 훅은 Python 3 표준 라이브러리만 +씁니다(decision-16). ## ✅ 필요조건을 말씀드립니다 @@ -164,7 +117,7 @@ git-format은 커밋 단계까지만 다룬다는 점을 말씀드립니다 — | 훅 | python3가 PATH에 없을 때 | | --- | --- | -| `pre-commit`, `commit-msg` | 훅이 실패하고 git이 커밋을 막습니다 — 에러가 바로 보이는 안전한 실패입니다. | +| `prepare-commit-msg`, `commit-msg` | 훅이 실패하고 git이 커밋을 막습니다 — 에러가 바로 보이는 안전한 실패입니다. | | `post-commit` | 커밋이 이미 만들어진 뒤라 git이 훅의 실패를 반영하지 않습니다 — 커밋은 성공한 것처럼 보이지만 `Task-Id`/`AI-Model`/`Signed-off-by` 같은 트레일러가 조용히 누락됩니다. | GUI git 클라이언트(SourceTree, GitHub Desktop, IDE 내장 git 패널)는 셸 @@ -203,7 +156,7 @@ cd ~/my-project - **설치 상세, 커밋 메시지 규칙 전문, AI 귀속 트레일러 표, 커스터마이즈, 저장소 구조, 주의점·한계**는 `backlog doc list`에서 확인하실 수 있습니다. -- **설계 배경과 각 결정 이유**(decision-1~16)는 `backlog decision list`에서 +- **설계 배경과 각 결정 이유**는 `backlog decision list`에서 확인하실 수 있습니다. - **작업 단위와 진행 상황**은 `backlog board`에서 확인하실 수 있습니다. - **라이선스**는 MIT입니다(전문: [`LICENSE`](./LICENSE)) — 외부 문서를 원문 그대로 From f2540ea451824d3d391b5bf432272b60ad710feb Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:02:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[chore][backlog]=20GF-136=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=A6=9D=EA=B1=B0=20+=20Done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 203줄에서 163줄로 줄었고 테스트 93개 통과. 예시 footer를 자기 커밋에서 뽑아 대조해 정정했다 - 초안에 쓴 AI-Agent 병합 형식은 GF-128 예정이고 현재 사실이 아니어서 세 트레일러로 되돌렸다. 표의 "만드는 훅" 열이 현재 전부 post-commit이므로, GF-127/128이 검증과 트레일러 삽입을 옮기면 갱신해야 한다는 점을 남겼다. Task-Id: GF-136 Co-Authored-By: Claude Opus 5 AI-Tool: claude-code AI-Tool-Version: 2.1.267 Co-Authored-By: Claude AI-Model: claude-opus-5 Tokens-Used: 1337346 Tool-Calls: 2 Hooks-Commit: d03e696 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...54\354\204\261\355\225\234\353\213\244.md" | 73 +++++++++++++++---- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git "a/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" "b/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" index e235317..ac249d5 100644 --- "a/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-136 - README\353\245\274-\354\265\234\354\242\205-\354\273\244\353\260\213-\355\217\254\353\247\267-\354\244\221\354\213\254\354\234\274\353\241\234-\354\236\254\352\265\254\354\204\261\355\225\234\353\213\244.md" @@ -1,10 +1,10 @@ --- id: GF-136 title: README를 최종 커밋 포맷 중심으로 재구성한다 -status: To Do +status: Done assignee: [] created_date: '2026-09-26 07:55' -updated_date: '2026-09-26 07:57' +updated_date: '2026-09-26 08:01' labels: - docs dependencies: [] @@ -30,22 +30,63 @@ README가 구현 서술 중심이다 — "왜 만들었는지 / 무엇을 만들 ## Acceptance Criteria -- [ ] #1 최종적으로 완성되는 커밋 메시지 전문을 문서 앞부분에 제시한다 — 제목·본문·footer가 모두 채워진 실제 예시 하나 -- [ ] #2 각 필드를 표로 정리한다: 제목 구성요소(type, subsystem, 설명)와 트레일러 전체 -- [ ] #3 표에 각 필드의 의미, 언제(어느 훅의 어느 시점) 만들어지는지, 왜 필요한지를 함께 담는다 -- [ ] #4 사람이 직접 쓰는 필드와 훅이 자동으로 붙이는 필드를 구분해 표시한다 -- [ ] #5 값의 신뢰 수준(강제/서버 발급/자가신고/자동)을 표에 유지한다 — 기존 트레일러 표의 핵심 정보다 -- [ ] #6 GF-135가 늘린 분량을 압축한다: 언어 lint 범위 설명, 컨슈머 한계 인용 블록, prepare-commit-msg 중첩 불릿, 재설계 진행 알림 블록 -- [ ] #7 새 설명을 보태지 않는다 — 기존 내용의 재배치와 압축만 한다 -- [ ] #8 '언제 만들어지는지'는 현재 사실대로 쓴다 (트레일러 삽입은 post-commit, 메시지 검증은 commit-msg). GF-127/128이 옮기면 갱신해야 한다는 점을 태스크 노트에 남긴다 -- [ ] #9 README 순증 줄 수가 0 이하다 (GF-135 이전 대비) -- [ ] #10 필드 표에 그 필드를 만드는 git 훅 이름을 명시한다 (prepare-commit-msg / commit-msg / post-commit 중 어느 것인지) -- [ ] #11 git 커밋 훅 라이프사이클을 문서에 함께 정리한다 — 커밋 한 번에 훅이 도는 순서와 각 단계에서 커밋 객체가 존재하는지 여부 -- [ ] #12 라이프사이클에서 --no-verify가 어느 훅을 건너뛰는지 표시한다 — 어떤 필드가 우회 가능하고 어떤 필드가 불가능한지가 여기서 갈린다 +- [x] #1 최종적으로 완성되는 커밋 메시지 전문을 문서 앞부분에 제시한다 — 제목·본문·footer가 모두 채워진 실제 예시 하나 +- [x] #2 각 필드를 표로 정리한다: 제목 구성요소(type, subsystem, 설명)와 트레일러 전체 +- [x] #3 표에 각 필드의 의미, 언제(어느 훅의 어느 시점) 만들어지는지, 왜 필요한지를 함께 담는다 +- [x] #4 사람이 직접 쓰는 필드와 훅이 자동으로 붙이는 필드를 구분해 표시한다 +- [x] #5 값의 신뢰 수준(강제/서버 발급/자가신고/자동)을 표에 유지한다 — 기존 트레일러 표의 핵심 정보다 +- [x] #6 GF-135가 늘린 분량을 압축한다: 언어 lint 범위 설명, 컨슈머 한계 인용 블록, prepare-commit-msg 중첩 불릿, 재설계 진행 알림 블록 +- [x] #7 새 설명을 보태지 않는다 — 기존 내용의 재배치와 압축만 한다 +- [x] #8 '언제 만들어지는지'는 현재 사실대로 쓴다 (트레일러 삽입은 post-commit, 메시지 검증은 commit-msg). GF-127/128이 옮기면 갱신해야 한다는 점을 태스크 노트에 남긴다 +- [x] #9 README 순증 줄 수가 0 이하다 (GF-135 이전 대비) +- [x] #10 필드 표에 그 필드를 만드는 git 훅 이름을 명시한다 (prepare-commit-msg / commit-msg / post-commit 중 어느 것인지) +- [x] #11 git 커밋 훅 라이프사이클을 문서에 함께 정리한다 — 커밋 한 번에 훅이 도는 순서와 각 단계에서 커밋 객체가 존재하는지 여부 +- [x] #12 라이프사이클에서 --no-verify가 어느 훅을 건너뛰는지 표시한다 — 어떤 필드가 우회 가능하고 어떤 필드가 불가능한지가 여기서 갈린다 ## Definition of Done -- [ ] #1 python3 -m unittest 스위트 전체 통과 -- [ ] #2 문서에 적힌 커밋 예시가 실제로 생성되는 footer와 일치하는지 확인 +- [x] #1 python3 -m unittest 스위트 전체 통과 +- [x] #2 문서에 적힌 커밋 예시가 실제로 생성되는 footer와 일치하는지 확인 + +## Implementation Notes + + +## 주의: '언제 만들어지는지' 열은 지금 사실이고 곧 바뀐다 + +현재 구조는 재설계 중이다. 트레일러 삽입은 아직 post-commit이 하고(git commit --amend), 메시지 검증은 아직 commit-msg가 한다. 표의 '언제 만들어지는지' 열은 **현재 사실대로** 쓴다. + +GF-127이 검증을, GF-128이 트레일러 삽입을 prepare-commit-msg로 옮기면 그 열을 갱신해야 한다. GF-134가 최종 아키텍처 반영과 hooks/README.md를 맡는다. + + +## Final Summary + + +README를 구현 서술에서 **최종 산출물 중심**으로 재구성했다. 203줄 → 163줄(GF-135가 늘린 7줄까지 포함해 40줄 감소). + +## 구조 + +1. **완성되는 커밋 예시** — 제목·본문·footer가 모두 채워진 실제 커밋 하나. 사람이 쓰는 부분과 훅이 붙이는 부분을 화살표로 구분 +2. **필드 표 2개** — 제목·본문(사람이 씀, 규칙 포함)과 트레일러(훅이 붙임). 각 필드의 의미·왜 필요한가·만드는 훅·값의 출처·신뢰 수준 +3. **훅 라이프사이클 표** — 커밋 경로만, 커밋 객체 존재 여부와 `--no-verify` 스킵 여부를 열로 +4. **범위** — lint 제외와 컨슈머 한계를 한 문단으로 압축 + +## 라이프사이클 표가 설계를 설명한다 + +표에 '커밋 객체가 있는가'와 '--no-verify로 건너뛰나' 열을 넣으니 설계의 두 핵심이 표에서 바로 읽힌다 — 에디터가 `prepare-commit-msg`보다 **뒤**에 열려서 그 훅은 최종 메시지를 볼 수 없다는 것(그래서 에디터 경로를 거부한다), 그리고 4단계에서 커밋이 이미 만들어지므로 `post-commit`은 커밋을 막을 수 없다는 것(그래서 검사가 아니라 기록만 한다). + +28행짜리 전체 훅 목록은 커밋 경로 5행으로 줄였다. 나머지 23행은 전부 '—'였고, git-format이 커밋 단계만 다룬다는 원칙은 한 문장으로 충분하다. + +## 압축한 것 + +GF-135가 늘린 네 곳을 걷어냈다 — 언어 lint 범위 설명 두 문단, 컨슈머 한계 인용 블록, `prepare-commit-msg` 중첩 불릿, 재설계 진행 알림 블록. 사실은 손실되지 않았다(없어진 정보는 decision-18/23과 doc-15에 이미 있다). + +## 예시를 실제 값으로 검증했다 + +처음 초안에 `AI-Agent: claude-code/2.1.267 (claude-opus-5)`를 썼는데 **그건 GF-128 예정이고 현재 사실이 아니다.** 실제 footer는 아직 `AI-Tool`/`AI-Tool-Version`/`AI-Model` 세 줄로 나뉘어 있다. 자기 커밋의 footer를 뽑아 대조해 정정했다. + +## 갱신이 필요한 시점 + +표의 '만드는 훅' 열이 현재 전부 `post-commit`이다. GF-127이 검증을, GF-128이 트레일러 삽입을 `prepare-commit-msg`로 옮기면 이 열과 라이프사이클 표를 갱신해야 한다. GF-134가 최종 아키텍처 반영과 `hooks/README.md`를 맡는다. +