diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f699c2b..7174cbb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,6 +1,6 @@
name: test
-# git-format 자체의 개발용 CI(GF-21, decision-8). 컨슈머에 배포하는 verify.yml과
+# git-logbook 자체의 개발용 CI(GF-21, decision-8). 컨슈머에 배포하는 verify.yml과
# 달리 이건 이 저장소 자신의 훅 코드 품질만 검사한다 — 훅도 테스트도 Python 표준
# 라이브러리만 쓰므로(decision-16, decision-21) 테스트 프레임워크를 따로 설치하는
# 스텝이 없다. 아래 ruff 설치는 이 저장소 자신의 Python 코드를 검사하는 개발 도구다 —
diff --git a/.gitmessage b/.gitmessage
index 2f28102..615556c 100644
--- a/.gitmessage
+++ b/.gitmessage
@@ -39,4 +39,4 @@
#
# ── 이 템플릿 사용법 ──
# git config commit.template .gitmessage
-# (git-format install.sh를 사용하면 자동으로 설정됩니다 — GF-8 참고)
+# (git-logbook install.sh를 사용하면 자동으로 설정됩니다 — GF-8 참고)
diff --git a/README.md b/README.md
index 1136947..9ccdf5e 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,17 @@
-# 🧬 git-format
+# 📒 git-logbook
-**git 자체 기능만으로 동작하는 커밋 메시지 규칙 · 검증 · 이력 정형화 도구**
+**git 이력을 작업 로그로 만드는 도구 — 커밋 형식 · 출처 기록 · 이력 불변**
[](./LICENSE)
[](./hooks/)
[](./install.sh)
[](#-필요조건을-말씀드립니다)
-`core.hooksPath` · `commit.template` · `git interpret-trailers` 등 **git 자체 기능만으로**
-여러 저장소가 하나의 커밋 규칙을 공유할 수 있게 만들어 사용하고 있는 도구입니다.
+에이전트와 사람이 남기는 커밋을 정해진 양식으로 쓰고, 누가·무엇으로·얼마를 들여
+만들었는지 자동으로 기록하고, 한 번 남긴 기록은 다시 쓰지 않게 지키는 도구입니다.
+`core.hooksPath` · `git interpret-trailers` 등 **git 자체 기능**으로 동작합니다.
훅은 Python 3 표준 라이브러리만 쓰므로 설치할 패키지는 없지만, `python3` 자체는
필요합니다([필요조건](#-필요조건을-말씀드립니다)).
@@ -65,7 +66,7 @@ Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com>
| `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` | 완전 자동, 모든 커밋 |
+| `Hooks-Commit` | 이 커밋을 검사한 git-logbook 자체의 버전. 훅에 버그가 있었을 때 어느 커밋들이 그 훅을 거쳤는지 역추적합니다 | `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`가 해시 실재를 검증 | 검증됨 |
@@ -76,11 +77,11 @@ Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com>
## 🪝 커밋 한 번에 훅이 도는 순서
-> 공식 문서: [githooks(5)](https://git-scm.com/docs/githooks). git이 제공하는 훅은 28개지만
-> git-format은 커밋 단계만 다룹니다(decision-11, decision-12) — `git push`는 아무 훅도
-> 거치지 않는 평범한 push입니다.
+> 공식 문서: [githooks(5)](https://git-scm.com/docs/githooks). git이 제공하는 훅은 28개이고,
+> git-logbook의 훅은 지금 커밋 단계만 다룹니다. push된 이력의 보호는 원격 설정이 맡습니다
+> ([다루는 범위](#-다루는-범위)).
-| 순서 | 훅 | 커밋 객체가 있는가 | `--no-verify`로 건너뛰나 | git-format이 하는 일 |
+| 순서 | 훅 | 커밋 객체가 있는가 | `--no-verify`로 건너뛰나 | git-logbook이 하는 일 |
| --- | --- | --- | --- | --- |
| 1 | [`prepare-commit-msg`](hooks/prepare-commit-msg) | 아직 없음 | **건너뛸 수 없음** | 재생·병합 커밋 면제, 에디터 경로 거부, 검증 마커 기록 |
| 2 | (에디터) | 아직 없음 | — | 사람이 메시지를 씁니다. 훅보다 **뒤**라서 1번은 최종 메시지를 볼 수 없습니다 |
@@ -95,11 +96,26 @@ Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com>
## 🎯 다루는 범위
-커밋 메시지 형식과 트레일러입니다. **언어별 lint는 다루지 않습니다**(decision-23) —
-커밋 형식을 맞추는 도구가 언어 도구를 돌릴 이유가 없고, 그 결합이 컨슈머에게
-npm/ruff/clang-format/mvn/sqlfluff의 존재를 전제하게 만들었습니다. 언어 검사가 필요하면
-CI에서 돌리시기 바랍니다 — git-format이 `core.hooksPath`를 점유하므로 그 저장소의
-`.git/hooks/*`는 무시되고, 자기 훅과 함께 쓰는 방법은 아직 정하지 않았습니다.
+git 이력을 작업 로그로 만드는 데 필요한 세 가지입니다(decision-25).
+
+| 기둥 | 하는 일 | 지금 있는 것 | 계획 |
+| --- | --- | --- | --- |
+| **형식** | 사람이 쓰는 제목·본문을 정해진 양식으로 | 제목·본문 검증, 에디터 경로 거부, 커밋 단위 규칙(decision-22, 강제 없음) | 검증을 `prepare-commit-msg`로 통합(GF-127) |
+| **출처 기록** | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남김 | 위 트레일러 전체, `Task-Id` 브랜치 강제, `AI-Model` 게이트 | 측정 재작성(GF-129), 트레일러 집합 재정의(decision-19, `Signed-off-by`는 유지 — decision-25) |
+| **이력 불변** | 한 번 남긴 기록은 다시 쓰지 않음(append-only, decision-24) | 이 저장소의 GitHub 설정: 머지 커밋만 허용, main의 force push·삭제 금지(doc-20). 컨슈머는 doc-20을 참고해 직접 설정 | 로컬 훅 강제(DRAFT-18 append-only 훅 강제), CI 트레일러 검사(DRAFT-19) |
+
+세 기둥은 서로를 필요로 합니다. 이력이 다시 쓰이면 트레일러가 자기 커밋에 대해 거짓이
+되고, 기록할 내용이 정형화돼 있지 않으면 지킬 가치가 줄어듭니다. 로컬 훅은
+`--no-verify`를 막지 못합니다. 그렇게 다시 쓴 이력이 main에 force push되는 것은 원격
+설정이 거부합니다. 웹 UI 커밋과 트레일러 검사는 CI로 할 계획입니다(DRAFT-19).
+
+**언어별 lint는 다루지 않습니다**(decision-23) — 세 기둥 어디에도 속하지 않고, 컨슈머에게
+npm/ruff/clang-format 같은 도구를 전제하게 만듭니다. 언어 검사가 필요하면 CI에서 돌리시기
+바랍니다. git-logbook이 `core.hooksPath`를 점유하므로 그 저장소의 `.git/hooks/*`는
+무시되고, 자기 훅과 함께 쓰는 방법은 아직 정하지 않았습니다.
+
+> 이름을 git-format에서 git-logbook으로 바꿨습니다(decision-25). 설정 키와 파일명
+> (`gitformat.*`, `gitformat.conf`, `.gitformat-*`)은 기존 설치를 깨지 않도록 아직 예전 이름입니다(GF-133).
구성은 Python 훅 + 설정 파일 하나(`hooks/gitformat.conf`) + POSIX sh 설치
스크립트(`install.sh`)입니다. `core.hooksPath` · `init.templateDir` ·
@@ -135,17 +151,17 @@ python3는 이런 환경에서 보이지 않을 수 있습니다.
### 기존 저장소에 적용하는 방법입니다
```sh
-git clone https://github.com/amosQP/git-format.git ~/git-format # 원하는 위치에 한 번만 클론
+git clone https://github.com/coralstay/git-logbook.git ~/git-logbook # 원하는 위치에 한 번만 클론
cd ~/my-project
-~/git-format/install.sh
+~/git-logbook/install.sh
```
-대상 디렉터리를 인자로 주셔도 됩니다: `~/git-format/install.sh ~/my-project`.
+대상 디렉터리를 인자로 주셔도 됩니다: `~/git-logbook/install.sh ~/my-project`.
### 앞으로 만들 모든 새 저장소에 자동 적용하는 방법입니다
```sh
-~/git-format/install.sh --global
+~/git-logbook/install.sh --global
```
`git init`/`git clone`을 실행할 때마다 훅과 커밋 템플릿이 자동으로 심어집니다
diff --git a/backlog/config.yml b/backlog/config.yml
index b2b8b82..84ee07a 100644
--- a/backlog/config.yml
+++ b/backlog/config.yml
@@ -1,4 +1,4 @@
-project_name: "git-format"
+project_name: "git-logbook"
default_status: "To Do"
statuses: ["To Do", "In Progress", "Done"]
labels: []
diff --git "a/backlog/decisions/decision-24 - \354\235\264\353\240\245\354\235\200-append-only-\342\200\224-\353\263\221\355\225\251\354\235\200-\353\250\270\354\247\200-\354\273\244\353\260\213-rebase\353\212\224-\353\241\234\354\273\254\353\247\214-cherry-pick-\352\270\210\354\247\200.md" "b/backlog/decisions/decision-24 - \354\235\264\353\240\245\354\235\200-append-only-\342\200\224-\353\263\221\355\225\251\354\235\200-\353\250\270\354\247\200-\354\273\244\353\260\213-rebase\353\212\224-\353\241\234\354\273\254\353\247\214-cherry-pick-\352\270\210\354\247\200.md"
new file mode 100644
index 0000000..d6ce8ab
--- /dev/null
+++ "b/backlog/decisions/decision-24 - \354\235\264\353\240\245\354\235\200-append-only-\342\200\224-\353\263\221\355\225\251\354\235\200-\353\250\270\354\247\200-\354\273\244\353\260\213-rebase\353\212\224-\353\241\234\354\273\254\353\247\214-cherry-pick-\352\270\210\354\247\200.md"
@@ -0,0 +1,125 @@
+---
+id: decision-24
+title: '이력은 append-only — 병합은 머지 커밋, rebase는 로컬만, cherry-pick 금지'
+date: '2026-09-26 08:28'
+status: accepted
+---
+## Context
+
+이 프로젝트의 전제는 **에이전트가 프로그래밍하고 git이 그 작업의 로그로 동작한다**는
+것이다(README). 그런데 이력을 다시 쓰는 연산에 대한 정책이 없었고, 오히려 PR을 GitHub의
+rebase-merge로 병합하는 관행이 있었다.
+
+**그 관행이 실제로 기록을 거짓으로 만들고 있었다.** 실측(2026-09-26, main의 최근 3개 커밋):
+
+```
+커밋 72755d0 committer = coralstay <...> ← git이 기록한 실제 커미터
+ Signed-off-by: cpu-once <...> ← 트레일러가 주장하는 커미터
+%G? = N (세 커밋 모두)
+```
+
+`post-commit`이 로컬 커밋 시점의 커미터로 `Signed-off-by`를 썼는데, GitHub이 rebase로
+커밋을 다시 만들면서 커미터가 바뀌었다. **트레일러가 자기가 앉아 있는 커밋에 대해 거짓을
+말한다.**
+
+같은 논리가 다른 트레일러에도 적용된다. `Tokens-Used`는 "그 세션 그 구간"의 측정값이고
+`Hooks-Commit`은 "이 커밋을 검사한 훅 버전"인데, 재생된 객체는 그 세션에서 만들어진 것도
+아니고 그 훅이 검사한 것도 아니다.
+
+## 외부 근거
+
+- **Linux 커널 메인테이너 문서**: "History that has been exposed to the world beyond your
+ private system should usually not be changed." / "Reparenting a patch series ... invalidates
+ much of the testing that was done." / "The kernel community is not scared by seeing merge
+ commits in its development history." — rebase로 merge를 피하려는 시도 자체를 문제로 본다.
+ 단 금지 선은 "공개된 이력"이다.
+- **서명**: rebase는 부모를 바꾸고 부모는 서명 대상이므로 서명이 무효화된다. GitHub의
+ rebase-merge는 서명 검증 없이 커밋을 추가한다(GitHub이 서명 키를 갖고 있지 않다).
+- **provenance 연구**(arXiv 2607.02774): force-push 1억 6,671만 건 / 저장소 1,992만 개.
+ 이력 재작성이 "이력은 불변"이라는 가정을 무너뜨려 저자 귀속과 취약점 추적 분석을
+ 무효화한다.
+- **Gerrit `Change-Id`**: 커밋 SHA가 불안정하다는 전제로 SHA와 독립된 안정 식별자를
+ 트레일러에 둔 선례. 그 `commit-msg` 훅 규칙이 우리와 같다 — 이미 있으면 건드리지 않는다.
+
+## Decision
+
+**커밋이 만들어진 뒤에는 그 객체를 다시 만들지 않는다(append-only).**
+
+금지선은 커널 문서와 같이 **공개(push)된 이력**에 둔다 — 아직 push하지 않은 로컬 작업
+정리는 로그 위조가 아니다.
+
+| 연산 | 정책 | 판별 방법 (전부 실측, doc 참고) |
+| --- | --- | --- |
+| 커밋 | 허용 | — |
+| **revert** | **권장** — append-only의 되돌리기 수단 | 제목 `Revert "` + 본문 `This reverts commit <실재 해시>` |
+| **merge** | 허용 — 통합 수단 | `MERGE_HEAD` |
+| **rebase (push 전 로컬)** | 허용 | `CHERRY_PICK_HEAD` + `$GIT_DIR/rebase-merge/` 디렉터리 |
+| **rebase (push 후)** | 거부 | `pre-rebase`에서 재생 대상이 원격 ref에 이미 있는지 |
+| **cherry-pick** | 거부 | `CHERRY_PICK_HEAD` 있고 `rebase-*/` **없음** |
+| **`--amend`** | 거부 | `source=commit` |
+| **squash merge** | 거부 | `source=squash` / `SQUASH_MSG` |
+
+**PR 병합은 머지 커밋으로 한다.** rebase-merge와 squash는 쓰지 않는다. 선형 뷰가 필요하면
+`git log --first-parent`를 쓴다.
+
+**이미 어긋난 과거 커밋은 고치지 않는다.** 그것을 고치는 것 자체가 이력 재작성이고, 그때의
+결정에는 그때의 이유가 있었다.
+
+## 강제 계층 — 로컬과 원격이 맞물리는 지점
+
+**`--no-verify`는 로컬 훅을 건너뛸 수 있다.** 그래서 로컬만으로는 정책이 완결되지 않는다.
+계층을 나눈다.
+
+### 로컬 (git 훅) — 볼 수 있는 모든 재작성 경로를 막는다
+
+| 대상 | 판별 |
+| --- | --- |
+| cherry-pick | `CHERRY_PICK_HEAD` 있고 `rebase-merge/`·`rebase-apply/` 없음 |
+| `--amend` | `source=commit` + `$3`이 `HEAD` + `rebase-merge/` 없음 |
+| squash | `source=squash` 또는 `SQUASH_MSG` |
+| 에디터 경로 | 메시지 파일에 비주석 내용 없음(GF-125) |
+| push된 커밋을 다시 쓰는 rebase | `pre-rebase`에서 재생 대상이 원격 ref에 있는지 |
+
+`--no-verify`로 건너뛸 수 있는 것은 `pre-rebase`와 `pre-push`다. `prepare-commit-msg`는
+건너뛸 수 없으므로 커밋 단계 강제는 그대로 유지된다(doc-15).
+
+### 원격 (GitHub) — 로컬이 닿지 않는 것을 막는다
+
+**github.com에는 `pre-receive` 훅을 설치할 수 없다** — 그건 GitHub Enterprise 전용 기능이다.
+대신 rulesets와 저장소 설정이 그 자리를 대신한다. 2026-09-26 적용 완료:
+
+| 설정 | 값 | 무엇을 막나 |
+| --- | --- | --- |
+| `allow_merge_commit` | `true` | — |
+| `allow_rebase_merge` | **`false`** | rebase-merge가 커밋을 다시 만들어 트레일러를 거짓으로 만드는 것 |
+| `allow_squash_merge` | **`false`** | squash |
+| ruleset `non_fast_forward` | 활성 | **force push** — `--no-verify`로 로컬 훅을 건너뛰어 다시 쓴 이력이 원격에 도달하는 것 |
+| ruleset `deletion` | 활성 | 브랜치 삭제 |
+
+**이게 두 계층이 맞물리는 지점이다.** `--no-verify`로 로컬에서 이력을 다시 쓰는 것 자체는
+막을 수 없다. 그러나 다시 쓴 결과는 **fast-forward가 아니므로 원격이 거부한다.** 즉 로컬
+재작성은 가능하지만 **공개될 수 없다** — 금지선을 "공개된 이력"에 둔 것과 정확히 일치한다.
+
+**켜면 안 되는 규칙**: rulesets의 `required_linear_history`는 이름이 좋아 보이지만
+**merge commit을 금지하는 규칙**이다. append-only와 정반대다.
+
+**켜지 않은 것**: `required_signatures`(서명 강제). 현재 이 저장소의 커밋은 `%G?`가 `N`으로
+나오므로 켜면 자기 push가 막힌다. 서명 설정을 먼저 정리한 뒤 검토할 사항이다.
+
+원격 설정의 현재 상태, 확인·변경 명령, 함정은 **doc-20(GitHub 원격 강제 설정 관리)** 에
+정리했다. 연산별 판별 방법과 우회 경로 전수 조사는 doc-19다.
+
+## Consequences
+
+- 트레일러가 자기 커밋에 대해 참인 상태가 유지된다. `Signed-off-by`가 실제 커미터와,
+ `Hooks-Commit`이 실제로 검사한 훅과, `Tokens-Used`가 실제 그 작업 구간과 맞물린다.
+- 서명이 병합 후에도 유효하게 남는다.
+- 실제 분기 구조가 보존된다 — rebase가 만드는 가짜 선형 순서 대신, 병렬로 일어난 작업이
+ 병렬로 기록된다. 에이전트 작업 이력을 분석할 때 이게 사실과 일치한다.
+- **기존 정책이 뒤집힌다.** 로컬 claude-rails 훅이 `git merge`를 `--ff-only` 없이 거부하고
+ 있고, 유저 메모리에 "항상 rebase-merge" 지시가 있었다. 둘 다 갱신해야 한다.
+- **cherry-pick과 rebase는 훅 인자로 구분되지 않는다** — `source`와 상태 파일이 동일하고
+ 진행 디렉터리(`rebase-merge/`)만 다르다. cherry-pick만 막으려면 그 디렉터리를 봐야 한다.
+- **merge와 revert는 git이 메시지를 지어주므로** `[type][subsystem]` 규칙에 맞지 않는다.
+ 둘 다 새 커밋이라 트레일러는 붙여야 하고 제목 형식 검사만 예외로 둬야 한다.
+- `git log`가 머지 커밋으로 덜 선형이 된다. `--first-parent`로 완화한다.
diff --git "a/backlog/decisions/decision-25 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\200-\355\230\225\354\213\235\302\267\354\266\234\354\262\230-\352\270\260\353\241\235\302\267\354\235\264\353\240\245-\353\266\210\353\263\200-\342\200\224-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205-decision-11\302\26712\354\235\230-\353\262\224\354\234\204-\354\240\234\354\231\270-\354\241\260\355\225\255-\353\214\200\354\262\264.md" "b/backlog/decisions/decision-25 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\200-\355\230\225\354\213\235\302\267\354\266\234\354\262\230-\352\270\260\353\241\235\302\267\354\235\264\353\240\245-\353\266\210\353\263\200-\342\200\224-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205-decision-11\302\26712\354\235\230-\353\262\224\354\234\204-\354\240\234\354\231\270-\354\241\260\355\225\255-\353\214\200\354\262\264.md"
new file mode 100644
index 0000000..8c3e8e9
--- /dev/null
+++ "b/backlog/decisions/decision-25 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\200-\355\230\225\354\213\235\302\267\354\266\234\354\262\230-\352\270\260\353\241\235\302\267\354\235\264\353\240\245-\353\266\210\353\263\200-\342\200\224-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205-decision-11\302\26712\354\235\230-\353\262\224\354\234\204-\354\240\234\354\231\270-\354\241\260\355\225\255-\353\214\200\354\262\264.md"
@@ -0,0 +1,78 @@
+---
+id: decision-25
+title: 프로젝트 목적은 형식·출처 기록·이력 불변 — git-logbook으로 개명 (decision-11·12의 범위 제외 조항 대체)
+date: "2026-10-03 02:58"
+status: accepted
+---
+
+## Context
+
+README와 이름(git-format)은 이 프로젝트를 "커밋 메시지 형식을 정하는 도구"로 소개해왔다.
+decision-23의 제목도 "git-format은 커밋 형식만 다룬다"였다. 그런데 실제 설계는 그보다 넓다.
+
+- append-only 이력 정책과 GitHub 원격 강제(decision-24, DRAFT-18/19/20)
+- 출처를 측정해 남기는 트레일러: `Tokens-Used`/`Tool-Calls`, `AI-Model`, `Hooks-Commit`
+- 커밋을 막는 게이트: `AI-Model` 화이트리스트, `Task-Id` 브랜치명 강제(decision-4), 에디터 경로 거부(decision-18)
+- 작업 단위 규칙: 커밋 하나 = 논리적 변경 하나(decision-22)
+
+2026-10-03 범위 점검에서 이것들이 "형식만"이라는 서술과 어긋난다는 게 드러났다. 특히
+decision-24와 DRAFT-19는 decision-11(서버측·CI 검증은 범위 밖), decision-12(push 단계는
+다루지 않음)와 정면으로 충돌하는데, 그 조항을 대체한다는 기록이 없었다.
+
+유저 판단: **이것들까지 포함하는 것이 이 프로젝트의 목적이다.** 서술을 설계에 맞춘다.
+
+## Decision
+
+**목적: git 이력을 에이전트와 사람의 작업 로그(logbook)로 만든다.** 세 기둥으로 이뤄진다.
+
+| 기둥 | 무엇을 하나 | 해당하는 것 |
+| --------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| 형식 | 사람이 쓰는 부분을 정해진 양식으로 | 제목·본문 규칙, 에디터 경로 거부, 커밋 단위(decision-22) |
+| 출처 기록 | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남긴다 | 트레일러 전체, 토큰 측정, `AI-Model` 게이트, `Task-Id` 브랜치 강제 |
+| 이력 불변 | 한 번 남긴 기록은 다시 쓰지 않는다 | append-only(decision-24), 로컬 훅 강제(DRAFT-18), 원격 강제(rulesets, DRAFT-19/20) |
+
+세 기둥은 서로를 필요로 한다. 출처 트레일러는 이력이 다시 쓰이면 자기 커밋에 대해 거짓이
+되고(decision-24의 실측), 이력 불변은 기록할 내용이 정형화돼 있어야 의미가 있다.
+
+**decision-11·12의 범위 제외 조항을 대체한다.**
+
+- decision-11의 "서버측/CI 기반 검증은 이 프로젝트 범위 밖": 대체한다. 원격 강제는 이력
+ 불변 기둥의 일부다 — 로컬 훅은 `--no-verify`와 웹 UI 커밋을 막지 못하므로 원격 없이는
+ 기둥이 완결되지 않는다(decision-24 "강제 계층").
+- decision-12의 "push 단계는 다루지 않는다": 대체한다. push된 이력의 보호가 범위에
+ 들어온다 — 원격 rulesets가 non-fast-forward push를 거부하고(decision-24), 로컬에서는
+ `pre-rebase`가 push 여부를 보고 재작성을 막는다(DRAFT-18). 새 push 단계 훅(pre-push)을
+ 두는 것은 아니다.
+- 두 decision의 나머지 판단은 유지한다. pre-push에서 테스트·빌드를 돌리지 않는다.
+- **미결**: CI 트레일러 검사(DRAFT-19)를 이 저장소 전용으로 둘지, 컨슈머에게 배포할지는
+ 정하지 않았다. DRAFT-19에서 정한다.
+
+**decision-19의 `Signed-off-by` 제거 조항을 대체한다 — `Signed-off-by`는 유지한다.**
+decision-19(2026-09-25)는 "커미터 정보는 커밋 객체에 이미 있다"며 제거를 정했다. 그 뒤의
+decision-24(09-26)는 `Signed-off-by`와 실제 커미터의 불일치를 이력 재작성의 증거로 썼고,
+DRAFT-19(09-26)는 그 대조를 CI 검사 항목으로 삼았다. 유저 규칙: 문서끼리 충돌하면 가장
+최근 문서를 우선한다. 출처 기록 기둥에서도 이 트레일러는 "기록 당시의 커미터"를 남겨
+재작성 여부를 사후에 대조하게 해 준다. decision-19의 나머지 조항(`AI-Agent` 병합,
+`Verify-Bypassed` 제거, 키 단위 중복 판정)은 그대로 유효하다.
+
+decision-23의 언어별 lint 제거는 유지한다. 언어 lint는 세 기둥 어디에도 속하지 않는다.
+대체되는 것은 그 제목의 "커밋 형식만"이라는 범위 표현뿐이다.
+
+**이름을 git-logbook으로 바꾼다.** git-format은 첫 번째 기둥만 말한다. 항해일지(logbook)는
+정해진 양식으로, 순서대로 쓰고, 지난 장은 고치지 않는다 — 세 기둥이 한 단어에 담긴다.
+GitHub 저장소는 새로 만들지 않고 rename한다(이력·PR·rulesets 유지, 예전 URL 리다이렉트).
+
+## Consequences
+
+- README의 소개와 "다루는 범위"를 세 기둥으로 다시 쓴다.
+- 이번에 바꾸지 않는 이름이 있다.
+ - 설정 키·파일명(`gitformat.*`, `gitformat.conf`, `.gitformat-verified`,
+ `.gitformat-token-cursor`, `_GITFORMAT_AMEND_GUARD`): 컨슈머의 git config를 깨는 호환성 변경이라 GF-133에서 다룬다.
+ - 태스크 접두어 `GF`: 바꾸면 기존 커밋의 `Task-Id`와 어긋난다. 지난 기록을 고치는 것은
+ 이력 불변과 반대다.
+ - 지난 decision·doc·task 본문의 "git-format": 당시의 기록이라 그대로 둔다.
+- 범위 판단 기준이 생긴다. 새 기능은 세 기둥 중 어디에 속하는지로 판단한다. 어디에도
+ 속하지 않으면(decision-23의 언어 lint처럼) 범위 밖이다.
+- 원격 강제가 범위에 들어오면서 GitHub 의존이 생긴다. decision-11이 피하려던 벤더 결합이다.
+ 로컬 훅이 1차 강제이고 원격은 로컬이 닿지 않는 곳만 막는다는 계층 구분(decision-24)으로
+ 결합을 최소로 둔다.
diff --git "a/backlog/docs/doc-1 - \354\204\244\354\271\230-\352\260\200\354\235\264\353\223\234.md" "b/backlog/docs/doc-1 - \354\204\244\354\271\230-\352\260\200\354\235\264\353\223\234.md"
index 677f54e..8014ba9 100644
--- "a/backlog/docs/doc-1 - \354\204\244\354\271\230-\352\260\200\354\235\264\353\223\234.md"
+++ "b/backlog/docs/doc-1 - \354\204\244\354\271\230-\352\260\200\354\235\264\353\223\234.md"
@@ -3,24 +3,24 @@ id: doc-1
title: 설치 가이드
type: guide
created_date: '2026-09-19 05:27'
-updated_date: '2026-09-19 09:33'
+updated_date: '2026-10-03 03:16'
---
## 🚀 설치
### 기존 저장소에 적용
```sh
-git clone https://github.com/amosQP/git-format.git ~/git-format # 원하는 위치에 한 번만 클론
+git clone https://github.com/coralstay/git-logbook.git ~/git-logbook # 원하는 위치에 한 번만 클론
cd ~/my-project
-~/git-format/install.sh
+~/git-logbook/install.sh
```
-대상 디렉터리를 인자로 줘도 됩니다: `~/git-format/install.sh ~/my-project`.
+대상 디렉터리를 인자로 줘도 됩니다: `~/git-logbook/install.sh ~/my-project`.
### 앞으로 만들 모든 새 저장소에 자동 적용
```sh
-~/git-format/install.sh --global
+~/git-logbook/install.sh --global
```
`git init`/`git clone`을 실행할 때마다 훅과 커밋 템플릿이 자동으로 심어집니다
@@ -35,10 +35,10 @@ cd ~/my-project
| 대상 | 명령 | 효과 |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------ |
-| 로컬(대상 저장소) | `git config core.hooksPath /hooks` | `.git/hooks/`의 기존 로컬 훅을 완전히 대체 |
-| 로컬(대상 저장소) | `git config commit.template /.gitmessage` | 커밋 에디터에 스켈레톤 표시 |
-| 전역(`--global`) | `git config --global init.templateDir /template` | 이후 모든 신규 저장소에 자동 적용 |
-| 전역(`--global`) | `git config --global commit.template /.gitmessage` | 위와 동일, 전역 기본값 |
+| 로컬(대상 저장소) | `git config core.hooksPath /hooks` | `.git/hooks/`의 기존 로컬 훅을 완전히 대체 |
+| 로컬(대상 저장소) | `git config commit.template /.gitmessage` | 커밋 에디터에 스켈레톤 표시 |
+| 전역(`--global`) | `git config --global init.templateDir /template` | 이후 모든 신규 저장소에 자동 적용 |
+| 전역(`--global`) | `git config --global commit.template /.gitmessage` | 위와 동일, 전역 기본값 |
**실행 중 새로 생기는 파일**
@@ -46,7 +46,7 @@ cd ~/my-project
| ------------------------- | ---------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------- |
| `.gitformat-verified` | `<대상 저장소>/.git/` | `pre-commit` 통과 시 생성, `post-commit`이 곧 삭제 | 커밋 사이에 남지 않는 임시 마커 |
| `.gitformat-token-cursor` | `<대상 저장소>/.git/` | Claude Code 실측 성공 시에만 `post-commit`이 갱신(`unavailable`일 때는 갱신 안 함) | `Tokens-Used`/`Tool-Calls` 델타 계산용 커서(누적 줄 수), 커밋 간 유지됨 |
-| `template/hooks/*` | 이 git-format 클론 자신의 `template/` 안 | `install.sh --global` 실행 시 | 클론 위치를 가리키는 심볼릭 링크, 커밋 안 됨(`.gitignore`) |
+| `template/hooks/*` | 이 git-logbook 클론 자신의 `template/` 안 | `install.sh --global` 실행 시 | 클론 위치를 가리키는 심볼릭 링크, 커밋 안 됨(`.gitignore`) |
**커밋 자체가 바뀌는 경우**: `post-commit`이 조건에 따라 `git commit --amend`로
방금 만든 커밋의 footer에 트레일러를 추가합니다(README의 "훅 생애주기" 섹션과
diff --git "a/backlog/docs/doc-19 - \354\235\264\353\240\245\354\235\204-\353\260\224\352\276\270\353\212\224-git-\354\227\260\354\202\260\353\263\204-\355\233\205-\355\214\220\353\263\204-\353\260\251\353\262\225-\354\213\244\354\270\241.md" "b/backlog/docs/doc-19 - \354\235\264\353\240\245\354\235\204-\353\260\224\352\276\270\353\212\224-git-\354\227\260\354\202\260\353\263\204-\355\233\205-\355\214\220\353\263\204-\353\260\251\353\262\225-\354\213\244\354\270\241.md"
new file mode 100644
index 0000000..f833431
--- /dev/null
+++ "b/backlog/docs/doc-19 - \354\235\264\353\240\245\354\235\204-\353\260\224\352\276\270\353\212\224-git-\354\227\260\354\202\260\353\263\204-\355\233\205-\355\214\220\353\263\204-\353\260\251\353\262\225-\354\213\244\354\270\241.md"
@@ -0,0 +1,201 @@
+---
+id: doc-19
+title: 이력을 바꾸는 git 연산별 훅 판별 방법 (실측)
+type: specification
+created_date: '2026-09-26 08:29'
+updated_date: '2026-09-26 12:55'
+---
+# 이력을 바꾸는 git 연산별 훅 판별 방법
+
+decision-24(append-only)를 구현하려면 훅이 각 연산을 **구분할 수 있어야** 한다. 어떤 연산이
+훅에게 어떻게 보이는지 실측한 결과다(git 2.54.0, 2026-09-26). 용어는 doc-14, 훅 실행 순서는
+doc-15 참고.
+
+## 결론 표
+
+| 연산 | `$2` (source) | `$GIT_DIR`의 파일 | `$GIT_DIR`의 디렉터리 | 정책 |
+| --- | --- | --- | --- | --- |
+| `git commit -m` / `-F` | `message` | — | — | 허용 |
+| `git commit` (에디터) | `template` 또는 **없음** | — | — | 거부(GF-125) |
+| `git commit --amend` | `commit` | — | — | **거부** |
+| `git revert --no-edit` | `message` | `MERGE_MSG` | — | **권장** |
+| `git revert` (에디터) | `merge` | `MERGE_MSG` | — | 권장 |
+| `git revert` 충돌 후 커밋 | `message` | `REVERT_HEAD` | — | 권장 |
+| **`git cherry-pick`** | `message` | `CHERRY_PICK_HEAD`, `MERGE_MSG` | **없음** | **거부** |
+| **rebase 재생** | `message` | `CHERRY_PICK_HEAD`, `MERGE_MSG` | **`rebase-merge/`** | 로컬만 허용 |
+| merge 커밋 (`-m` 없이) | `merge` | `MERGE_HEAD`, `MERGE_MSG` | — | 허용 |
+| merge 상태 + `git commit -m` | `message` | `MERGE_HEAD`, `MERGE_MSG` | — | 허용 |
+| squash | `squash` | `SQUASH_MSG` | — | 거부 |
+
+## 가장 중요한 사실: cherry-pick과 rebase는 인자로 구분되지 않는다
+
+rebase는 내부적으로 각 커밋을 cherry-pick으로 재생한다. 그래서 **`source`도 같고 상태 파일도
+같다.**
+
+```
+사용자 cherry-pick : files: CHERRY_PICK_HEAD MERGE_MSG dirs:
+rebase 재생 : files: CHERRY_PICK_HEAD MERGE_MSG dirs: rebase-merge/
+```
+
+차이는 **진행 상태 디렉터리 하나뿐**이다. git은 rebase 진행을 `$GIT_DIR/rebase-merge/`에
+기록한다(`git status`가 "rebase in progress"를 아는 방법이다). 구 backend(`--apply`)는
+`rebase-apply/`를 쓰므로 둘 다 확인해야 한다.
+
+**따라서 cherry-pick만 거부하려면**: `CHERRY_PICK_HEAD`가 있고 `rebase-merge/`와
+`rebase-apply/`가 **둘 다 없을 때만** 거부한다. 상태 파일만 보고 거부하면 로컬 rebase까지
+막힌다.
+
+`REBASE_HEAD`는 믿을 수 없다 — rebase 재생 중에 **존재하지 않았다**(초기 조사에서 있다고
+기록했으나 실측으로 틀렸음).
+
+## revert의 세 갈래
+
+`REVERT_HEAD`는 **revert가 충돌로 멈췄을 때만** 생긴다. 깔끔한 revert에는 없다.
+
+```
+$ git revert --no-edit HEAD
+source=[message] MERGE_MSG msg=[Revert "[feat] base commit"]
+```
+
+그래서 `MERGE_HEAD`/`CHERRY_PICK_HEAD`/`REBASE_HEAD`/`REVERT_HEAD` 네 파일만 검사하는 조건은
+**깔끔한 revert를 잡지 못한다.**
+
+그리고 revert 커밋에는 **지금 트레일러가 붙는다**(실측):
+
+```
+Revert "[feat] base commit"
+
+This reverts commit 91023bd7c790...
+
+AI-Tool: claude-code / AI-Tool-Version: 2.1.267 / Co-Authored-By: Claude
+Tokens-Used: unavailable (transcript-not-found) / Tool-Calls: unavailable (...)
+Hooks-Commit: 72755d0 / Signed-off-by: t
+```
+
+revert에서는 `prepare-commit-msg`와 `post-commit`이 돌고 **`commit-msg`는 돌지 않는다.**
+그래서 이 제목은 지금까지 형식 검사를 받은 적이 없다. 검증을 `prepare-commit-msg`로 옮기면
+`Revert "`로 시작하는 제목이 `[type][subsystem]` 규칙에 걸려 **revert가 전부 막힌다.**
+
+→ revert는 새 커밋이므로 **트레일러는 붙이고 제목 형식 검사만 예외**로 둔다. 판별은 좁게:
+제목이 `Revert "`로 시작하고 본문에 `This reverts commit <해시>`가 있고 그 해시가 실재할 때만.
+merge 커밋(`Merge branch ...`)도 같은 예외가 필요하다.
+
+## `pre-rebase`는 rebase를 막을 수 있다
+
+```
+pre-rebase argc=1 upstream=[main] branch=[]
+rebase exit=1 → HEAD 그대로 (막힘)
+```
+
+인자는 upstream 하나뿐이고, **현재 브랜치를 rebase할 때 `$2`(branch)는 빈 값**이다.
+`exit 1`이 rebase 전체를 중단시킨다 — 커밋이 만들어지기 전이므로 "이미 push된 커밋을 다시
+쓰는 rebase"를 막을 자리는 여기다.
+
+판정 방법(제안): `git rev-list ..HEAD`로 재생 대상을 구하고, 그중 하나라도
+`git branch -r --contains `에 걸리면 이미 공개된 커밋이므로 거부한다.
+
+## 재현 절차
+
+저장소 **밖**에 훅 디렉터리를 두고 측정한다. 훅을 저장소 안에 두고 커밋하면 revert가
+**훅 파일 자체를 삭제**해서 "훅이 안 돌았다"고 오판한다(실제로 걸렸던 함정).
+
+```sh
+mkdir -p /tmp/hk && cat > /tmp/hk/prepare-commit-msg <<'H'
+#!/bin/sh
+{ printf "source=[%s] files:" "$2"
+ for f in MERGE_HEAD CHERRY_PICK_HEAD REBASE_HEAD REVERT_HEAD MERGE_MSG SQUASH_MSG; do
+ [ -f "$(git rev-parse --git-dir)/$f" ] && printf " %s" "$f"; done
+ printf " dirs:"
+ for d in rebase-merge rebase-apply sequencer; do
+ [ -d "$(git rev-parse --git-dir)/$d" ] && printf " %s/" "$d"; done
+ printf " | %s\n" "$(head -1 "$1")"; } >> /tmp/hooklog
+H
+chmod +x /tmp/hk/prepare-commit-msg
+# 대상 저장소에서: git config core.hooksPath /tmp/hk
+```
+
+**측정 함정 둘**: (1) cherry-pick으로 같은 변경을 이미 올려놓고 rebase를 측정하면, git이
+"이미 적용됨"으로 보고 커밋을 건너뛰어 훅이 안 돈다 — 매번 깨끗한 저장소에서 측정할 것.
+(2) 전역 `commit.template` 유무로 `source` 값이 달라진다 — `GIT_CONFIG_GLOBAL=/dev/null`로
+격리할 것(GF-125에서 CI와 로컬이 갈린 원인이다).
+
+## decision-24의 우회 경로 전수 조사 (실측, 2026-09-26)
+
+이력을 만들거나 바꿀 수 있는 git 명령을 훑어 정책이 닿지 않는 곳을 정리했다.
+
+### A. 훅이 아예 없는 경로
+
+| 명령 | 무엇을 하나 | 훅 |
+| --- | --- | --- |
+| `git stash` | 커밋 객체를 **만든다** | **없음** (실측) |
+| `git commit-tree` | 커밋을 직접 만든다 | **없음** (실측) |
+| `git fast-import`, `hash-object`+`update-ref` | 대량 생성 | 없음 |
+| `git reset --hard`, `git update-ref` | 커밋을 만들지 않고 브랜치를 옮겨 이력을 버린다 | 없음 |
+| `git filter-branch`, `git filter-repo` | 이력 전체를 다시 쓴다 | 없음 |
+| `git replace` | 객체를 바꾸지 않고 **보이는 이력**을 바꾼다 | 없음 |
+| `git reflog expire`, `git gc --prune=now` | 복구 경로를 파괴한다 | 없음 |
+
+이 부류는 git 훅의 공통 한계다. 훅은 보안 경계가 아니다 — 막을 수 없고, 막으려 해서도 안 된다.
+문서에 알려진 한계로 적는다.
+
+### B. 훅이 돌지만 판별이 안 되는 경로
+
+**`git cherry-pick -n` 후 수동 커밋** — 실측 결과 상태 파일이 **하나도 없다**:
+
+```
+cherry-pick -n 후 수동 커밋: source=[message] files: dirs:
+```
+
+`CHERRY_PICK_HEAD`가 없으므로 **평범한 커밋과 구별할 수 없다.** cherry-pick 차단의 우회
+경로다. `git revert -n`도 같은 부류일 것으로 보이나 미측정.
+
+**`git commit -C ` vs `--amend`** — 둘 다 `source=commit`이다. 구분은 `$3`로 한다:
+
+```
+--amend : source=[commit] $3=[HEAD]
+commit -C HEAD~1 : source=[commit] $3=[HEAD~1]
+```
+
+`GIT_REFLOG_ACTION`은 설정되지 않았다(확인). 남는 모호성: `git commit -C HEAD`는 `--amend`와
+완전히 같게 보인다. 드문 명령이고 "새 커밋에 HEAD의 메시지를 재사용"이라 어차피 제 메시지를
+쓰게 하는 편이 낫다 — 오탐으로 수용하고 문서에 적는다.
+
+### C. 로컬 훅이 도달할 수 없는 경로 — 가장 큰 구멍
+
+**GitHub 병합 버튼은 로컬 훅을 전혀 거치지 않는다.** 실측으로 이 저장소의 현재 설정:
+
+```
+allow_merge_commit: true
+allow_rebase_merge: true ← Signed-off-by 불일치를 만든 원인
+allow_squash_merge: true ← decision-24가 금지한 연산
+브랜치 보호: 없음 (main에 force push 가능)
+```
+
+**decision-24는 훅만으로 달성할 수 없다.** GitHub 저장소 설정에서 rebase-merge와 squash-merge를
+끄고 merge commit만 남겨야 하고, `main`에 브랜치 보호(force push 금지)를 걸어야 한다.
+이건 코드가 아니라 설정이다. 웹 UI의 파일 편집도 같은 부류다.
+
+### D. 우회 의도가 없어도 막히는 오탐
+
+**`rebase -i`의 reword는 rebase 안에서 `source=commit`을 만든다** (실측):
+
+```
+rebase -i reword:
+ [pre-rebase 실행됨]
+ source=[message] files: CHERRY_PICK_HEAD dirs: rebase-merge/ ← 재생
+ source=[commit] files: dirs: rebase-merge/ ← reword(내부 amend)
+```
+
+amend 거부를 `source=commit`만으로 하면 **`rebase -i`가 깨진다.** `rebase-merge/` 디렉터리가
+있으면 면제해야 한다.
+
+### E. 정책 자체의 충돌
+
+로컬 claude-rails 훅이 `git merge`를 `--ff-only` 없이 거부한다. 이 조사 중 실제로 두 번 막혀
+merge 케이스를 측정조차 할 수 없었다. decision-24의 "병합은 머지 커밋" 정책과 정면 충돌하므로
+그 훅을 먼저 갱신해야 한다(claude-rails 저장소).
+
+## 원격 쪽 강제 설정
+
+이 조사에서 드러난 "로컬 훅이 도달할 수 없는 경로"는 GitHub 설정으로 막았다. 무엇이 왜
+걸려 있고 어떻게 확인·변경하는지는 **doc-20(GitHub 원격 강제 설정 관리)** 에 있다.
diff --git "a/backlog/docs/doc-20 - GitHub-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\204\244\354\240\225-\352\264\200\353\246\254-\342\200\224-rulesets\354\231\200-\354\240\200\354\236\245\354\206\214-\354\204\244\354\240\225.md" "b/backlog/docs/doc-20 - GitHub-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\204\244\354\240\225-\352\264\200\353\246\254-\342\200\224-rulesets\354\231\200-\354\240\200\354\236\245\354\206\214-\354\204\244\354\240\225.md"
new file mode 100644
index 0000000..18aea05
--- /dev/null
+++ "b/backlog/docs/doc-20 - GitHub-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\204\244\354\240\225-\352\264\200\353\246\254-\342\200\224-rulesets\354\231\200-\354\240\200\354\236\245\354\206\214-\354\204\244\354\240\225.md"
@@ -0,0 +1,108 @@
+---
+id: doc-20
+title: GitHub 원격 강제 설정 관리 — rulesets와 저장소 설정
+type: guide
+created_date: '2026-09-26 12:54'
+updated_date: '2026-10-03 03:16'
+---
+# GitHub 원격 강제 설정 관리
+
+decision-24(append-only)는 **로컬 훅만으로 달성할 수 없다.** `--no-verify`가 로컬 훅을
+건너뛸 수 있고, GitHub의 병합 버튼은 로컬 훅을 아예 거치지 않는다. 그 몫을 원격 설정이
+맡는다. 이 문서는 무엇이 왜 걸려 있고 어떻게 확인·변경하는지를 적는다.
+
+## 전제: github.com에는 `pre-receive` 훅이 없다
+
+서버측 훅으로 push를 검사하는 기능은 **GitHub Enterprise 전용**이다. github.com에서는
+**rulesets**와 **저장소 설정**이 그 자리를 대신한다.
+
+- [Available rules for rulesets](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)
+- [Working with pre-receive hooks (Enterprise 전용)](https://docs.github.com/en/enterprise-server@3.0/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/working-with-pre-receive-hooks)
+
+## 현재 걸려 있는 것 (2026-09-26 적용)
+
+### 저장소 설정 — 병합 방식
+
+| 설정 | 값 | 왜 |
+| --- | --- | --- |
+| `allow_merge_commit` | `true` | append-only가 허용하는 유일한 병합 방식. 원본 커밋 객체를 그대로 두고 합치는 커밋만 얹는다 |
+| `allow_rebase_merge` | **`false`** | 커밋을 다시 만들어 SHA·커미터를 바꾼다. 실측으로 이 저장소의 `Signed-off-by`가 실제 커미터와 어긋난 원인이다 |
+| `allow_squash_merge` | **`false`** | 여러 커밋을 하나로 합쳐 커밋 단위 기록을 파괴한다 |
+
+### ruleset `main-protection` (id 23313297, active)
+
+| 규칙 | 무엇을 막나 |
+| --- | --- |
+| `non_fast_forward` | **force push.** `--no-verify`로 로컬 훅을 건너뛰어 다시 쓴 이력이 원격에 도달하는 것을 막는다 |
+| `deletion` | 브랜치 삭제 |
+
+대상: `~DEFAULT_BRANCH`(= `main`).
+
+## 두 계층이 맞물리는 지점
+
+`--no-verify`는 `pre-rebase`와 `pre-push`를 건너뛴다. 즉 **로컬에서 이력을 다시 쓰는 것
+자체는 막을 수 없다.** 그러나 다시 쓴 결과는 fast-forward가 아니므로 **원격이 거부한다.**
+
+로컬 재작성은 가능하지만 **공개될 수 없다** — decision-24가 금지선을 "공개된 이력"에 둔 것과
+정확히 일치한다. `prepare-commit-msg`는 `--no-verify`로 건너뛸 수 없으므로 커밋 단계 강제는
+로컬에서 그대로 유지된다(doc-15).
+
+## 켜면 안 되는 규칙
+
+| 규칙 | 왜 안 되나 |
+| --- | --- |
+| `required_linear_history` | 이름이 좋아 보이지만 **merge commit을 금지하는** 규칙이다. squash 또는 rebase 병합만 허용하게 되어 append-only와 정반대가 된다 |
+
+## 아직 켜지 않은 것
+
+| 규칙 | 상태 | 조건 |
+| --- | --- | --- |
+| `required_signatures` | 끔 | 현재 이 저장소 커밋은 `%G?`가 `N`으로 나온다. 켜면 자기 push가 막힌다. 서명 설정(ssh-agent에 키 등록 등)을 먼저 정리한 뒤 검토 |
+| `pull_request` (PR 필수) | 끔 | 로컬 claude-rails 훅이 이미 main 직접 push를 막고 있다. 이중으로 걸 가치가 있는지 판단 필요 |
+| `required_status_checks` | 끔 | CI 통과를 병합 조건으로 강제할지. 지금은 사람이 확인하고 병합한다 |
+| 대상을 `~ALL`로 확대 | 끔 | 현재 `main`만 보호한다. task 브랜치까지 force push를 막으면 "push 후 rebase 금지"가 완전해지지만, 운영 부담을 먼저 판단해야 한다 |
+
+## 확인 방법
+
+```sh
+# 병합 방식
+gh api repos/coralstay/git-logbook \
+ --jq '{allow_merge_commit, allow_rebase_merge, allow_squash_merge}'
+
+# ruleset 목록
+gh api repos/coralstay/git-logbook/rulesets \
+ --jq '.[] | "\(.name) | \(.enforcement) | id=\(.id)"'
+
+# 특정 ruleset 상세
+gh api repos/coralstay/git-logbook/rulesets/23313297 \
+ --jq '{name, enforcement, include: .conditions.ref_name.include, rules: [.rules[].type]}'
+```
+
+## 변경 방법
+
+```sh
+# 병합 방식 변경
+gh api -X PATCH repos/coralstay/git-logbook \
+ -f allow_merge_commit=true -F allow_rebase_merge=false -F allow_squash_merge=false
+
+# ruleset 갱신 (PUT은 전체 교체다 - 기존 규칙을 빠뜨리면 사라진다)
+gh api -X PUT repos/coralstay/git-logbook/rulesets/23313297 --input ruleset.json
+```
+
+## 함정
+
+**ruleset의 `conditions.ref_name.include`가 비어 있으면 아무 브랜치에도 적용되지 않는다.**
+`main-protection`이 실제로 그 상태로 `disabled`로 있었다 — 그냥 활성화만 하면 규칙이 걸린
+것처럼 보이지만 대상이 없어 아무 일도 하지 않는다. 활성화할 때 `include`를 반드시 확인할 것.
+
+**`PUT`은 전체 교체다.** 규칙 하나를 추가하려고 `PUT`을 쓰면서 기존 규칙을 빠뜨리면 그
+규칙이 사라진다.
+
+**저장소 설정과 ruleset은 다른 레이어다.** 병합 방식은 저장소 설정(`allow_*_merge`)이고,
+force push 차단은 ruleset이다. 한쪽만 보면 정책이 걸려 있다고 오판한다.
+
+## 관련 문서
+
+- decision-24 — append-only 이력 정책과 강제 계층
+- doc-19 — 이력을 바꾸는 git 연산별 훅 판별 방법(실측)과 우회 경로 전수 조사
+- doc-15 — 훅 실행 경로 실측
diff --git "a/backlog/docs/doc-5 - \354\240\200\354\236\245\354\206\214-\352\265\254\354\241\260.md" "b/backlog/docs/doc-5 - \354\240\200\354\236\245\354\206\214-\352\265\254\354\241\260.md"
index 5aa8017..10d184d 100644
--- "a/backlog/docs/doc-5 - \354\240\200\354\236\245\354\206\214-\352\265\254\354\241\260.md"
+++ "b/backlog/docs/doc-5 - \354\240\200\354\236\245\354\206\214-\352\265\254\354\241\260.md"
@@ -3,32 +3,27 @@ id: doc-5
title: 저장소 구조
type: guide
created_date: '2026-09-19 05:27'
-updated_date: '2026-09-24 18:53'
+updated_date: '2026-10-03 03:16'
---
## 📁 저장소 구조
```
-git-format/
+git-logbook/
├── hooks/ # core.hooksPath가 가리키는 실제 훅(Python 3, decision-16)
│ ├── readme.md # 훅 3개의 역할·생애주기와 python3 실행 요구사항
-│ ├── pre-commit
-│ ├── commit-msg
-│ ├── post-commit
-│ ├── gitformat.conf # 내부 기본값 한 곳에 모음(git config 포맷)
-│ └── checks/
-│ ├── readme.md # 언어 감지·디스패치와 스크립트별 검사 내용
-│ └── {ts,python,java,cpp,sql}.py
+│ ├── prepare-commit-msg # 재생 커밋 면제, 에디터 경로 거부(decision-18)
+│ ├── commit-msg # 메시지 형식·Task-Id·AI-Model 검증
+│ ├── post-commit # 트레일러 삽입
+│ └── gitformat.conf # 내부 기본값 한 곳에 모음(git config 포맷, 이름은 GF-133에서 변경 예정)
├── template/ # init.templateDir용 (hooks/*는 install.sh --global이 생성)
-├── .gitmessage # commit.template
-├── install.sh # POSIX sh 유지(decision-16) + python3 존재 확인 가드
-├── tests/ # bats-core 테스트(dev 전용, decision-8)
-├── .github/workflows/ # test.yml - 이 저장소 자신의 dev용 CI(shellcheck + ruff
-│ # + bats)뿐, 컨슈머에게 제공하는 서버사이드 검증
-│ # 기능은 없음(decision-11)
+├── .gitmessage # commit.template (GF-132에서 제거 예정)
+├── install.sh # POSIX sh 유지(decision-9) + python3 존재 확인 가드
+├── tests/ # Python unittest(dev 전용, decision-21)
+├── .github/workflows/ # test.yml - 이 저장소 자신의 dev용 CI(shellcheck + ruff + unittest)
└── backlog/ # 이 저장소 자체 개발 관리(decision, task)
```
-훅 3개와 `checks/*.py`는 모두 Python 3이고 표준 라이브러리만 쓴다(decision-16).
+훅 3개는 모두 Python 3이고 표준 라이브러리만 쓴다(decision-16).
셔뱅이 `#!/usr/bin/env python3`이므로 훅이 실행되는 시점의 PATH에서 `python3`가
잡혀야 한다. `install.sh`만 POSIX sh를 유지하며, 그래서 CI의 shellcheck 대상도
이 파일 하나다.
diff --git "a/backlog/docs/doc-6 - \354\243\274\354\235\230\354\240\220\352\263\274-\355\225\234\352\263\204.md" "b/backlog/docs/doc-6 - \354\243\274\354\235\230\354\240\220\352\263\274-\355\225\234\352\263\204.md"
index c8e4c47..5d7f904 100644
--- "a/backlog/docs/doc-6 - \354\243\274\354\235\230\354\240\220\352\263\274-\355\225\234\352\263\204.md"
+++ "b/backlog/docs/doc-6 - \354\243\274\354\235\230\354\240\220\352\263\274-\355\225\234\352\263\204.md"
@@ -3,21 +3,19 @@ id: doc-6
title: 주의점과 한계
type: guide
created_date: '2026-09-19 05:27'
-updated_date: '2026-09-25 03:25'
+updated_date: '2026-10-03 03:16'
---
## ⚠️ 주의점
- **`core.hooksPath`는 로컬 훅을 완전히 대체합니다.** 기존에 `.git/hooks/`에 다른 훅을
쓰고 있었다면 install.sh 실행 전에 충돌 여부를 확인하세요.
-- **push 단계는 아예 훅하지 않습니다.** git-format은 `commit-msg`/`pre-commit`/
- `post-commit`(커밋 단계)까지만 다루고, `git push`는 평범한 push입니다(decision-12) —
- 테스트/빌드 실행이나 `--no-verify` 탐지 같은 것도 없습니다. 로컬 훅은 애초에
- `rm -rf .git/hooks`로도 완전히 우회 가능하므로, git-format의 보장은 "우회
- 불가능"이 아니라 "정상적인 사용에서 흔적을 남긴다"는 것뿐입니다. push 단계까지
- 막는 서버사이드 백스톱(예: CI 필수 status check, 서버 pre-receive 훅)은 이
- 프로젝트 범위 밖입니다 — git-format은 클라이언트측 훅만 제공합니다(decision-11).
- 필요하면 컨슈머가 직접 구성해야 하고, `hooks/commit-msg`/`hooks/pre-commit`을
- 그대로 호출하는 방식으로 재사용할 수 있습니다.
+- **로컬 훅은 우회할 수 있고, push된 이력은 원격 설정이 지킵니다.** 훅은 지금 커밋
+ 단계(`prepare-commit-msg`/`commit-msg`/`post-commit`)만 다루고, `--no-verify`나
+ `core.hooksPath` 해제로 건너뛸 수 있습니다. 이력 불변은 원격 강제까지 포함해야 완결되므로
+ 범위 안입니다(decision-25가 decision-11·12의 범위 제외 조항을 대체). 지금 원격 강제는
+ 이 저장소 main의 force push·삭제 금지와 머지 커밋 전용 병합뿐입니다(doc-20). 컨슈머는
+ doc-20을 참고해 자기 원격을 직접 설정해야 합니다. 웹 UI 커밋과 트레일러 검사는 CI로 할
+ 계획입니다(DRAFT-19).
- **`post-commit`이 커밋 해시를 amend로 바꿀 수 있습니다.** Verify-Bypassed나 AI 귀속
트레일러가 붙을 때마다 커밋이 한 번 더 amend됩니다 — 커밋 해시를 미리 캐싱하는
외부 도구가 있다면 이 점을 인지해야 합니다.
@@ -36,12 +34,9 @@ updated_date: '2026-09-25 03:25'
모든 훅이 stdout/stderr을 UTF-8로 고정하므로 죽지 않습니다(GF-116). 대신 UTF-8을
못 읽는 터미널에서는 글자가 깨져 보일 수 있습니다 — 커밋을 막는 것보다 낫다고
판단한 트레이드오프입니다.
-- **지원 언어는 TS/Python/Java/C·C++/SQL 5종으로 고정돼 있습니다.** 확대 계획은
- 없습니다.
-- **push 단계 검증(테스트/빌드 포함)은 의도적으로 이 프로젝트 범위 밖입니다**
- (decision-11, decision-12). git-format은 커밋 단계(`commit-msg`/`pre-commit`/
- `post-commit`)까지만 다룹니다 — 필요하면 컨슈머가 자체 CI나 서버 pre-receive
- 훅에서 `hooks/commit-msg`/`hooks/pre-commit`을 직접 호출해 구성해야 합니다.
+- **언어별 lint는 하지 않습니다**(decision-23). 언어 검사가 필요하면 CI에서 돌리세요.
+- **push 단계에서 테스트·빌드는 돌리지 않습니다**(decision-12에서 유지되는 부분). 무거운
+ 검사는 컨슈머의 CI 몫입니다.
- **커밋 이력을 반정형 데이터로 남기는 것까지가 이 프로젝트의 범위입니다.** 그
데이터를 실제로 파싱하거나 학습용으로 가공하는 도구는 포함돼 있지 않습니다.
- **`AI-Model`/`Tokens-Used` 측정은 Claude Code의 문서화되지 않은 내부 경로 규칙에
diff --git "a/backlog/docs/doc-7 - \354\213\244\354\202\254\354\232\251-\354\230\210\354\213\234-\354\233\214\355\201\254\354\212\244\353\243\250.md" "b/backlog/docs/doc-7 - \354\213\244\354\202\254\354\232\251-\354\230\210\354\213\234-\354\233\214\355\201\254\354\212\244\353\243\250.md"
index c5e4910..e4a8f99 100644
--- "a/backlog/docs/doc-7 - \354\213\244\354\202\254\354\232\251-\354\230\210\354\213\234-\354\233\214\355\201\254\354\212\244\353\243\250.md"
+++ "b/backlog/docs/doc-7 - \354\213\244\354\202\254\354\232\251-\354\230\210\354\213\234-\354\233\214\355\201\254\354\212\244\353\243\250.md"
@@ -3,8 +3,11 @@ id: doc-7
title: 실사용 예시 워크스루
type: guide
created_date: '2026-09-19 05:27'
-updated_date: '2026-09-25 16:46'
+updated_date: '2026-10-03 03:16'
---
+> 이 워크스루는 언어 lint 제거(GF-135)와 prepare-commit-msg 도입(GF-125) 이전 구조를
+> 기준으로 쓰였습니다. 재작성은 GF-134에서 합니다.
+
## 📌 실제 사용법
설치가 끝나면 평소 하던 `git checkout`/`git add`/`git commit`/`git push`를
@@ -94,6 +97,5 @@ git log -1
Hooks-Commit: b5bf03a
```
-git-format은 커밋 단계까지만 다룹니다 — `git push`는 아무 훅도 거치지 않는
-평범한 push입니다(decision-12). push 단계 검증이 필요하면 컨슈머가 직접
-CI나 서버측으로 구성해야 합니다(doc-6 "주의점과 한계" 참고).
+훅은 커밋 단계까지만 돕니다. push된 이력의 보호는 원격 설정이 맡고, 컨슈머는
+doc-20을 참고해 직접 설정합니다(decision-25, doc-6 "주의점과 한계" 참고).
diff --git "a/backlog/docs/doc-8 - decision-\354\235\264\353\240\245-\354\247\200\353\217\204.md" "b/backlog/docs/doc-8 - decision-\354\235\264\353\240\245-\354\247\200\353\217\204.md"
index 777ecf2..b9d10df 100644
--- "a/backlog/docs/doc-8 - decision-\354\235\264\353\240\245-\354\247\200\353\217\204.md"
+++ "b/backlog/docs/doc-8 - decision-\354\235\264\353\240\245-\354\247\200\353\217\204.md"
@@ -3,7 +3,7 @@ id: doc-8
title: decision 이력 지도
type: guide
created_date: '2026-09-19 05:31'
-updated_date: '2026-09-25 02:51'
+updated_date: '2026-10-03 03:16'
---
## backlog/ 디렉토리, 왜 헷갈리는가
@@ -56,12 +56,17 @@ Consequences)에만 산문으로 적혀 있다. 17개 decision 중 7개 주제
| 주제 | 지금 유효 | 대체된 것 |
| --- | --- | --- |
| 커밋 메시지 스타일 | decision-10 | decision-1 |
-| 서버사이드 검증 범위 | decision-11 | decision-3의 5번 항목 |
-| pre-push 존치 | decision-12 | decision-11의 판단 |
+| 서버사이드 검증 범위 | decision-25 | decision-11의 범위 제외(← decision-3의 5번 항목) |
+| pre-push 존치 | decision-12(테스트·빌드 미실행 부분만), decision-25 | decision-11의 판단, decision-12의 push 단계 제외 |
| 외부 문서 vendoring | decision-9(원안) | decision-13 → decision-14가 되돌림(2단 반전) |
| 견고성 테스트 전략 | decision-8 | decision-7 |
| AI 귀속 footer | decision-5 + 본문 내 Amendment(GF-13, GF-97) | decision-15·decision-17은 대체 아니고 보완 |
| hooks 구현 언어 | decision-16 | decision-9의 POSIX 문법 유지 정책(hooks/* 범위만) |
+| 우회 탐지 | decision-18 | decision-3(검증 마커·Verify-Bypassed) |
+| 테스트 프레임워크 | decision-21 | decision-8의 bats-core 채택 |
+| 트레일러 집합 | decision-19, decision-25(Signed-off-by 유지) | decision-10의 Signed-off-by 조항 → decision-19의 Signed-off-by 제거 조항 |
+| 언어 lint | decision-23 | decision-6 |
+| 프로젝트 목적·이름 | decision-25 | decision-23 제목의 "커밋 형식만" 범위 표현 |
각 행의 근거(해당 decision 파일 본문을 직접 대조):
@@ -148,3 +153,13 @@ decision-1(대체됨) · decision-2(standalone) · decision-3(부분 대체됨,
(유효) · decision-15(decision-5를 보완, 그 자체로 유효) · decision-16(decision-9를
hooks/* 범위에서 대체, 유효) · decision-17(decision-5·decision-15를 보완, 그 자체로
유효) — 17개 모두 위 표 또는 목록 중 하나에 속한다.
+
+### decision-18~25 (2026-09-25 이후)
+
+decision-18(decision-3을 대체, 유효) · decision-19(decision-10의 Signed-off-by 조항을
+대체, 자신의 Signed-off-by 제거 조항은 decision-25가 대체, 나머지 유효) · decision-20
+(standalone) · decision-21(decision-8의 bats 채택을 대체) · decision-22(standalone) ·
+decision-23(decision-6을 대체) · decision-24(standalone) · decision-25(decision-11·12의
+범위 제외 조항과 decision-19의 Signed-off-by 제거 조항을 대체). 위 "17개 decision 전부
+계정"의 decision-11·decision-12 상태는 이 목록이 우선한다. 문서끼리 충돌하면 가장 최근
+문서를 우선한다.
diff --git "a/backlog/drafts/draft-18 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205\354\235\204-\355\233\205\354\234\274\353\241\234-\352\260\225\354\240\234\355\225\234\353\213\244.md" "b/backlog/drafts/draft-18 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205\354\235\204-\355\233\205\354\234\274\353\241\234-\352\260\225\354\240\234\355\225\234\353\213\244.md"
new file mode 100644
index 0000000..5824abd
--- /dev/null
+++ "b/backlog/drafts/draft-18 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205\354\235\204-\355\233\205\354\234\274\353\241\234-\352\260\225\354\240\234\355\225\234\353\213\244.md"
@@ -0,0 +1,63 @@
+---
+id: DRAFT-18
+title: append-only 이력 정책을 훅으로 강제한다
+status: Draft
+assignee: []
+created_date: '2026-09-26 08:30'
+updated_date: '2026-09-26 12:55'
+labels:
+ - hooks
+ - policy
+dependencies: []
+references:
+ - decision-24
+documentation:
+ - backlog/docs/doc-19 - 이력을-바꾸는-git-연산별-훅-판별-방법-실측.md
+ - backlog/docs/doc-13 - git-format-재설계-계획-—-커밋-규칙을-prepare-commit-msg로-통합.md
+ - backlog/docs/doc-15 - 훅-실행-경로-실측-git-2.54.0.md
+ - backlog/docs/doc-20 - GitHub-원격-강제-설정-관리-—-rulesets와-저장소-설정.md
+priority: high
+type: feature
+---
+
+## Description
+
+
+decision-24로 이력 정책을 append-only로 정했다. 커밋이 만들어진 뒤에는 그 객체를 다시 만들지 않는다 — 금지선은 커널 문서와 같이 push된 이력에 둔다.
+
+이 태스크는 그 정책을 훅으로 강제한다. 판별 방법은 doc-19에 실측으로 정리돼 있다.
+
+가장 조심할 점: cherry-pick과 rebase는 훅 인자로 구분되지 않는다. source도 상태 파일도 같고 $GIT_DIR/rebase-merge/ 디렉터리 하나만 다르다. 상태 파일만 보고 거부하면 허용해야 할 로컬 rebase까지 막힌다.
+
+부수 효과: merge와 revert는 git이 메시지를 지어주므로(Merge branch ..., Revert "...") [type][subsystem] 규칙에 맞지 않는다. 둘 다 새 커밋이라 트레일러는 붙여야 하고 제목 형식 검사만 예외로 둬야 한다. 이 예외는 검증을 prepare-commit-msg로 옮기는 GF-127에서 함께 구현해야 한다 — 지금은 revert/merge에서 commit-msg가 돌지 않아 드러나지 않는다.
+
+기존 정책과 충돌한다: 로컬 claude-rails 훅이 git merge를 --ff-only 없이 거부하고 있다. 그 훅도 갱신해야 한다(claude-rails 저장소).
+
+
+## Acceptance Criteria
+
+- [ ] #1 cherry-pick을 거부한다 — CHERRY_PICK_HEAD가 있고 rebase-merge/와 rebase-apply/가 둘 다 없을 때만
+- [ ] #2 로컬 rebase 재생은 통과한다 (rebase-merge/ 디렉터리가 있으면 면제) — cherry-pick 차단이 rebase를 막지 않는지 테스트로 고정
+- [ ] #3 git commit --amend를 거부한다 (source=commit). post-commit이 내부적으로 쓰는 amend는 _GITFORMAT_AMEND_GUARD로 구분되어 통과한다
+- [ ] #4 squash를 거부한다 (source=squash 또는 SQUASH_MSG)
+- [ ] #5 hooks/pre-rebase를 신설해 이미 push된 커밋을 다시 쓰는 rebase를 거부한다 — git rev-list ..HEAD의 커밋 중 하나라도 git branch -r --contains에 걸리면 거부
+- [ ] #6 push되지 않은 로컬 rebase는 pre-rebase를 통과한다
+- [ ] #7 revert는 통과하고 트레일러가 붙는다 — append-only의 되돌리기 수단이다
+- [ ] #8 merge 커밋은 통과하고 트레일러가 붙는다
+- [ ] #9 거부 메시지가 왜 막혔는지와 대신 무엇을 쓰라는지 안내한다 (cherry-pick 대신 merge, amend 대신 새 커밋)
+- [ ] #10 README에 append-only 정책과 연산별 허용/거부를 표로 적는다
+- [ ] #11 cherry-pick -n 후 수동 커밋은 상태 파일이 남지 않아 판별 불가 — 알려진 우회 경로로 문서에 적는다(막으려 하지 않는다)
+- [ ] #12 amend 거부는 source=commit이면서 $3이 HEAD일 때만 한다 — commit -C 는 정당한 새 커밋이므로 통과시킨다
+- [ ] #13 amend 거부는 rebase-merge/ 디렉터리가 있으면 면제한다 — rebase -i의 reword가 내부적으로 source=commit을 만들기 때문
+- [ ] #14 GitHub 저장소 설정에서 rebase-merge와 squash-merge를 끄고 merge commit만 남긴다 — 훅으로는 달성 불가
+- [ ] #15 main에 브랜치 보호를 걸어 force push를 막는다
+- [ ] #16 stash/commit-tree/filter-branch/replace 등 훅이 없는 경로를 README의 알려진 한계에 적는다
+
+
+## Definition of Done
+
+- [ ] #1 python3 -m unittest 스위트 전체 통과
+- [ ] #2 ruff check 통과
+- [ ] #3 이 저장소 자신의 커밋이 정상 생성되는지 확인
+- [ ] #4 claude-rails의 --ff-only 강제 훅을 갱신해야 한다는 점을 그쪽 저장소에 드래프트로 남긴다
+
diff --git "a/backlog/drafts/draft-19 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\240\225\355\225\251\354\204\261\354\235\204-CI\354\227\220\354\204\234-\352\262\200\354\202\254\355\225\234\353\213\244-\342\200\224-required-status-check.md" "b/backlog/drafts/draft-19 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\240\225\355\225\251\354\204\261\354\235\204-CI\354\227\220\354\204\234-\352\262\200\354\202\254\355\225\234\353\213\244-\342\200\224-required-status-check.md"
new file mode 100644
index 0000000..996a08b
--- /dev/null
+++ "b/backlog/drafts/draft-19 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\240\225\355\225\251\354\204\261\354\235\204-CI\354\227\220\354\204\234-\352\262\200\354\202\254\355\225\234\353\213\244-\342\200\224-required-status-check.md"
@@ -0,0 +1,67 @@
+---
+id: DRAFT-19
+title: 트레일러 정합성을 CI에서 검사한다 — required status check
+status: Draft
+assignee: []
+created_date: '2026-09-26 13:22'
+updated_date: '2026-10-03 03:14'
+labels:
+ - ci
+ - policy
+dependencies: []
+references:
+ - decision-24
+documentation:
+ - backlog/docs/doc-20 - GitHub-원격-강제-설정-관리-—-rulesets와-저장소-설정.md
+ - backlog/docs/doc-19 - 이력을-바꾸는-git-연산별-훅-판별-방법-실측.md
+priority: high
+type: feature
+---
+
+## Description
+
+
+원격에는 트레일러가 참인지 검사하는 수단이 없다. rulesets에 커밋 메시지 패턴 규칙이 아예 없다(GitHub 문서 확인). 그래서 로컬 훅을 거치지 않은 커밋이 그대로 들어온다.
+
+## 막지 못하는 경로
+
+- GitHub 웹 UI의 파일 편집 → 커밋 생성. 로컬 훅을 전혀 거치지 않으므로 트레일러가 **하나도 없다**
+- 훅을 지운 클론에서의 push
+- 손으로 쓴 거짓 트레일러 — `Signed-off-by`를 아무 이름으로 쓰거나 `Hooks-Commit`에 없는 해시를 적어도 아무도 확인하지 않는다
+- 실제로 이 저장소에서 그 일이 일어났다: main의 커밋들이 `Signed-off-by: cpu-once`를 달고 있는데 실제 committer는 `coralstay`다(rebase-merge가 원인, 지금은 차단됨)
+
+## 왜 CI인가
+
+유저가 제안한 "트레일러를 직접 뜯어 마커와 실제 값이 맞물리는지 최종 검사"의 자리가 여기다. `post-commit`은 커밋이 이미 만들어진 뒤라 **막을 수 없다**. CI는 **완성된 커밋 객체를 보면서 required status check으로 병합을 막을 수 있는** 유일한 자리다.
+
+decision-24가 마커를 "지워질 훅의 오판을 막으려 살려둔 죽은 코드"로 만든 것도 여기서 해소된다 — 트레일러 자체가 직접 증거이므로 마커라는 대리 지표가 필요 없다.
+
+
+## Acceptance Criteria
+
+- [ ] #1 PR의 각 커밋에 대해 트레일러를 파싱해 검사한다 (git interpret-trailers --parse 또는 %(trailers) 포맷)
+- [ ] #2 Signed-off-by가 그 커밋의 실제 committer와 일치하는지 확인한다 — 불일치는 이력 재작성의 증거다
+- [ ] #3 Hooks-Commit이 git-format 저장소에 실재하는 커밋을 가리키는지 확인한다
+- [ ] #4 Task-Id가 브랜치명의 패턴과 일치하는지 확인한다
+- [ ] #5 같은 트레일러 키가 두 번 나타나지 않는지 확인한다
+- [ ] #6 제목이 [type][subsystem] 형식인지 확인한다 — 웹 UI 커밋은 로컬 검증을 거치지 않았다
+- [ ] #7 merge 커밋과 revert 커밋은 git이 메시지를 지어주므로 제목 형식 검사에서 제외한다
+- [ ] #8 GitHub Actions 워크플로로 구현하고 ruleset의 required_status_checks로 등록한다
+- [ ] #9 실패 메시지가 어느 커밋의 어느 트레일러가 왜 어긋났는지 지목한다
+
+
+## Definition of Done
+
+- [ ] #1 의도적으로 어긋난 트레일러를 가진 커밋으로 실제 PR을 만들어 CI가 막는지 확인
+- [ ] #2 정상 커밋으로 만든 PR은 통과하는지 확인
+
+
+## Comments
+
+
+author: @claude
+created: 2026-10-03 03:14
+---
+2026-10-03: decision-19가 Signed-off-by 제거를 정했지만, 이 드래프트(09-26)가 더 최근이라 유저 규칙(최근 문서 우선)에 따라 Signed-off-by는 유지된다 — decision-25에 기록. AC#2(Signed-off-by와 실제 committer 대조)는 유효하다. 컨슈머 배포 여부는 이 드래프트에서 정한다(decision-25 미결 항목).
+---
+
diff --git "a/backlog/drafts/draft-20 - \354\233\220\352\262\251-\353\263\264\355\230\270-\353\262\224\354\234\204-\355\231\225\353\214\200-\352\262\200\355\206\240-\342\200\224-task-\353\270\214\353\236\234\354\271\230-PR-\355\225\204\354\210\230-\354\204\234\353\252\205-\355\203\234\352\267\270.md" "b/backlog/drafts/draft-20 - \354\233\220\352\262\251-\353\263\264\355\230\270-\353\262\224\354\234\204-\355\231\225\353\214\200-\352\262\200\355\206\240-\342\200\224-task-\353\270\214\353\236\234\354\271\230-PR-\355\225\204\354\210\230-\354\204\234\353\252\205-\355\203\234\352\267\270.md"
new file mode 100644
index 0000000..3e550c0
--- /dev/null
+++ "b/backlog/drafts/draft-20 - \354\233\220\352\262\251-\353\263\264\355\230\270-\353\262\224\354\234\204-\355\231\225\353\214\200-\352\262\200\355\206\240-\342\200\224-task-\353\270\214\353\236\234\354\271\230-PR-\355\225\204\354\210\230-\354\204\234\353\252\205-\355\203\234\352\267\270.md"
@@ -0,0 +1,50 @@
+---
+id: DRAFT-20
+title: '원격 보호 범위 확대 검토 — task 브랜치, PR 필수, 서명, 태그'
+status: Draft
+assignee: []
+created_date: '2026-09-26 13:22'
+updated_date: '2026-09-26 13:22'
+labels:
+ - policy
+dependencies: []
+references:
+ - decision-24
+documentation:
+ - backlog/docs/doc-20 - GitHub-원격-강제-설정-관리-—-rulesets와-저장소-설정.md
+priority: medium
+type: spike
+---
+
+## Description
+
+
+현재 ruleset(main-protection)은 `~DEFAULT_BRANCH`만 보호한다. 원격 쪽에 남은 구멍을 정리하고 각각 켤지 판단한다.
+
+## 남은 구멍
+
+**task 브랜치가 보호되지 않는다.** force push로 다시 쓸 수 있다. 그 상태로 merge되면 main 자체는 append-only지만 **에이전트 작업 기록은 이미 위조된 상태**로 들어온다. 대상을 `~ALL`로 넓히면 "push된 이력은 불변"이 완전해지는데, 대신 PR 도중 main 변경을 따라잡을 때 rebase 후 force push를 못 하게 된다 — merge로 따라잡아야 한다(append-only와 일관되지만 운영 부담을 판단해야 한다).
+
+**main에 직접 push가 원격에서 막히지 않는다.** 지금은 로컬 claude-rails 훅만 막는다. 그 훅이 없는 환경이나 웹 UI에서는 그대로 들어간다. ruleset의 `pull_request` 규칙을 켜면 원격이 막는다.
+
+**저장소 설정 자체를 되돌릴 수 있다.** `allow_rebase_merge`를 다시 켜는 것을 막을 장치가 없다 — ruleset은 브랜치를 보호하지 저장소 설정을 보호하지 않는다. 조직 레벨 규칙이 필요한 영역이다.
+
+**서명이 강제되지 않는다.** 누가/무엇이 만든 커밋인지 암호학적으로 확인되지 않는다. 다만 현재 이 저장소 커밋은 `%G?`가 `N`으로 나오므로 켜면 자기 push가 막힌다 — 서명 설정 정리가 선행이다.
+
+**태그는 별도 target이다.** 브랜치 ruleset이 태그 삭제·이동을 커버하지 않는다.
+
+
+## Acceptance Criteria
+
+- [ ] #1 ruleset 대상을 ~ALL로 넓힐지 결정하고 근거를 기록한다 — PR 중 main을 따라잡을 때의 운영 부담을 함께 평가
+- [ ] #2 pull_request 규칙(PR 필수)을 켤지 결정한다
+- [ ] #3 required_signatures를 켤 조건을 정한다 — 현재 %G?가 N인 원인부터 해소
+- [ ] #4 태그 ruleset이 필요한지 판단한다
+- [ ] #5 저장소 설정이 되돌려지는 것을 감지할 방법이 있는지 조사한다 (조직 규칙 또는 주기적 확인)
+- [ ] #6 결정 결과를 doc-20에 반영한다
+
+
+## Definition of Done
+
+- [ ] #1 각 항목에 대해 켠다/안 켠다와 그 근거가 기록됐다
+
diff --git "a/backlog/drafts/draft-21 - \353\213\244\354\235\264\354\226\264\352\267\270\353\236\250-\354\206\214\354\212\244\353\245\274-\354\240\200\354\236\245\354\206\214\354\227\220-\353\223\244\354\235\264\352\263\240-\353\271\214\353\223\234-\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270\354\235\204-\354\204\270\354\232\264\353\213\244.md" "b/backlog/drafts/draft-21 - \353\213\244\354\235\264\354\226\264\352\267\270\353\236\250-\354\206\214\354\212\244\353\245\274-\354\240\200\354\236\245\354\206\214\354\227\220-\353\223\244\354\235\264\352\263\240-\353\271\214\353\223\234-\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270\354\235\204-\354\204\270\354\232\264\353\213\244.md"
new file mode 100644
index 0000000..1cba02d
--- /dev/null
+++ "b/backlog/drafts/draft-21 - \353\213\244\354\235\264\354\226\264\352\267\270\353\236\250-\354\206\214\354\212\244\353\245\274-\354\240\200\354\236\245\354\206\214\354\227\220-\353\223\244\354\235\264\352\263\240-\353\271\214\353\223\234-\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270\354\235\204-\354\204\270\354\232\264\353\213\244.md"
@@ -0,0 +1,42 @@
+---
+id: DRAFT-21
+title: 다이어그램 소스를 저장소에 들이고 빌드 파이프라인을 세운다
+status: Draft
+assignee: []
+created_date: '2026-10-03 02:41'
+updated_date: '2026-10-03 02:41'
+labels:
+ - docs
+ - tooling
+dependencies: []
+priority: medium
+type: docs
+---
+
+## Description
+
+
+훅 3개의 동작을 DOT 다이어그램 6개로 그려 아티팩트로 발행했는데(2026-10-03), 소스가 세션 스크래치패드에만 있다. 저장소에 들여야 diff·리뷰가 되고 훅을 고칠 때 다이어그램이 같이 갱신된다.
+
+원본은 diagrams/*.dot이고 렌더된 SVG는 생성물이다. 색은 .dot에 센티넬 hex로 적고 빌드가 CSS 변수로 치환한다 — SVG 하나가 문서의 라이트/다크 테마를 그대로 따라간다. 참고 구현은 coralstay-software-factory의 build-diagrams.py( 마커 사이를 교체하는 방식).
+
+결정이 필요한 지점은 주입 대상이다. GitHub은 마크다운 안의 인라인