From 522bf60423c48f79431b983a696b376eef034702 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:30:15 +0900 Subject: [PATCH 01/20] =?UTF-8?q?[docs][backlog]=20append-only=20=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=20=EC=A0=95=EC=B1=85=20decision=EA=B3=BC=20=EC=8B=A4?= =?UTF-8?q?=EC=B8=A1=20=EB=AC=B8=EC=84=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전제가 "git이 에이전트 작업의 로그"라면 이력 재작성은 기록 수정이다. 실측으로 지금 깨져 있는 것을 확인했다 - main의 커밋들이 Signed-off-by: cpu-once를 달고 있는데 실제 committer는 coralstay다. rebase-merge가 커밋을 다시 만들면서 트레일러가 거짓이 됐다. decision-24: 커밋 후 객체를 다시 만들지 않는다. 금지선은 커널 문서와 같이 push된 이력에 둔다. 병합은 머지 커밋, rebase는 로컬만, cherry-pick 금지, amend 금지, revert는 권장. doc-19: 연산별 판별 방법을 실측으로 정리했다. 핵심은 cherry-pick과 rebase가 훅 인자로 구분되지 않고 rebase-merge/ 디렉터리 하나로만 갈린다는 것이다. REBASE_HEAD는 rebase 재생 중 존재하지 않았다 - 초기 조사가 틀렸다. 아직 태스크가 아니다 - 승격은 별도 커밋으로 남긴다. Task-Id: GF-137 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: 43399706 Tool-Calls: 27 Hooks-Commit: 6a72e3b Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...4-cherry-pick-\352\270\210\354\247\200.md" | 81 ++++++++++++ ...1\353\262\225-\354\213\244\354\270\241.md" | 120 ++++++++++++++++++ ...25\354\240\234\355\225\234\353\213\244.md" | 56 ++++++++ 3 files changed, 257 insertions(+) create mode 100644 "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" create mode 100644 "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" create mode 100644 "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" 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..585da0f --- /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,81 @@ +--- +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`를 쓴다. + +**이미 어긋난 과거 커밋은 고치지 않는다.** 그것을 고치는 것 자체가 이력 재작성이고, 그때의 +결정에는 그때의 이유가 있었다. + +## 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/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..c67129e --- /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,120 @@ +--- +id: doc-19 +title: 이력을 바꾸는 git 연산별 훅 판별 방법 (실측) +type: specification +created_date: '2026-09-26 08:29' +updated_date: '2026-09-26 08:29' +--- +# 이력을 바꾸는 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와 로컬이 갈린 원인이다). 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..9e27126 --- /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,56 @@ +--- +id: DRAFT-18 +title: append-only 이력 정책을 훅으로 강제한다 +status: Draft +assignee: [] +created_date: '2026-09-26 08:30' +updated_date: '2026-09-26 08:30' +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 +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 정책과 연산별 허용/거부를 표로 적는다 + + +## Definition of Done + +- [ ] #1 python3 -m unittest 스위트 전체 통과 +- [ ] #2 ruff check 통과 +- [ ] #3 이 저장소 자신의 커밋이 정상 생성되는지 확인 +- [ ] #4 claude-rails의 --ff-only 강제 훅을 갱신해야 한다는 점을 그쪽 저장소에 드래프트로 남긴다 + From 29586498409cdf47115db8780dfe30590f96d3c8 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:46:27 +0900 Subject: [PATCH 02/20] =?UTF-8?q?[docs][backlog]=20decision-24=20=EC=9A=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B2=BD=EB=A1=9C=20=EC=A0=84=EC=88=98=20=EC=A1=B0?= =?UTF-8?q?=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이력을 만들거나 바꾸는 git 명령을 훑어 정책이 닿지 않는 곳을 doc-19에 정리했다. 다섯 부류로 나온다. 가장 큰 구멍은 GitHub 병합 버튼이다 - 로컬 훅을 전혀 거치지 않는다. 실측으로 이 저장소는 rebase-merge와 squash-merge가 둘 다 켜져 있고 브랜치 보호도 없다. decision-24는 훅만으로 달성할 수 없다. 판별 불가 경로도 찾았다. cherry-pick -n 후 수동 커밋은 상태 파일이 하나도 남지 않아 평범한 커밋과 구별되지 않는다. 오탐도 둘 찾았다. rebase -i의 reword가 rebase 안에서 source=commit을 만들어서, amend 거부를 그것만으로 하면 rebase -i가 깨진다. commit -C도 source=commit이라 $3이 HEAD인지로 구분해야 한다. Task-Id: GF-137 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: 20719965 Tool-Calls: 10 Hooks-Commit: 21f7c00 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...1\353\262\225-\354\213\244\354\270\241.md" | 78 ++++++++++++++++++- ...25\354\240\234\355\225\234\353\213\244.md" | 8 +- 2 files changed, 84 insertions(+), 2 deletions(-) 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" index c67129e..0a59344 100644 --- "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" @@ -3,7 +3,7 @@ id: doc-19 title: 이력을 바꾸는 git 연산별 훅 판별 방법 (실측) type: specification created_date: '2026-09-26 08:29' -updated_date: '2026-09-26 08:29' +updated_date: '2026-09-26 12:46' --- # 이력을 바꾸는 git 연산별 훅 판별 방법 @@ -118,3 +118,79 @@ chmod +x /tmp/hk/prepare-commit-msg "이미 적용됨"으로 보고 커밋을 건너뛰어 훅이 안 돈다 — 매번 깨끗한 저장소에서 측정할 것. (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 저장소). 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" index 9e27126..aa80e3d 100644 --- "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" @@ -4,7 +4,7 @@ title: append-only 이력 정책을 훅으로 강제한다 status: Draft assignee: [] created_date: '2026-09-26 08:30' -updated_date: '2026-09-26 08:30' +updated_date: '2026-09-26 12:46' labels: - hooks - policy @@ -45,6 +45,12 @@ decision-24로 이력 정책을 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 From 558b335724e1bf2f1d439ddafbf7d0b8c414302f Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:52:23 +0900 Subject: [PATCH 03/20] =?UTF-8?q?[docs][backlog]=20decision-24=EC=97=90=20?= =?UTF-8?q?=EA=B0=95=EC=A0=9C=20=EA=B3=84=EC=B8=B5=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?+=20=EC=9B=90=EA=B2=A9=20=EC=84=A4=EC=A0=95=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --no-verify는 로컬 훅을 건너뛸 수 있어 로컬만으로는 정책이 완결되지 않는다. 계층을 나눠 기록했다. github.com에는 pre-receive 훅을 설치할 수 없다 - GitHub Enterprise 전용 기능이다. rulesets와 저장소 설정이 그 자리를 대신한다. 적용: allow_rebase_merge와 allow_squash_merge를 끄고 merge commit만 남겼다. ruleset으로 force push와 브랜치 삭제를 막았다. 두 계층이 맞물리는 지점: --no-verify로 로컬 재작성은 막을 수 없지만, 다시 쓴 결과는 fast-forward가 아니라 원격이 거부한다. 금지선을 공개된 이력에 둔 것과 일치한다. required_linear_history는 켜면 안 된다 - merge commit을 금지하는 규칙이다. Task-Id: GF-137 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: 12109931 Tool-Calls: 6 Hooks-Commit: d1526c8 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...4-cherry-pick-\352\270\210\354\247\200.md" | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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" index 585da0f..09a21ec 100644 --- "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" @@ -65,6 +65,47 @@ rebase-merge로 병합하는 관행이 있었다. **이미 어긋난 과거 커밋은 고치지 않는다.** 그것을 고치는 것 자체가 이력 재작성이고, 그때의 결정에는 그때의 이유가 있었다. +## 강제 계층 — 로컬과 원격이 맞물리는 지점 + +**`--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가 막힌다. 서명 설정을 먼저 정리한 뒤 검토할 사항이다. + ## Consequences - 트레일러가 자기 커밋에 대해 참인 상태가 유지된다. `Signed-off-by`가 실제 커미터와, From 9ca09abe173857d392901606736efe499e60eb8f Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:55:42 +0900 Subject: [PATCH 04/20] =?UTF-8?q?[docs][backlog]=20doc-20=20=EC=9B=90?= =?UTF-8?q?=EA=B2=A9=20=EA=B0=95=EC=A0=9C=20=EC=84=A4=EC=A0=95=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=AC=B8=EC=84=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main-protection ruleset을 활성화했다. 규칙은 제가 만든 것과 동일했고 결정적으로 적용 대상 브랜치가 비어 있었다 - 그대로 켜면 아무 브랜치에도 적용되지 않는다. 대상을 채워 켜고 중복 ruleset은 지웠다. doc-20에 현재 걸려 있는 설정과 그 이유, 켜면 안 되는 규칙 (required_linear_history는 merge commit을 금지한다), 아직 켜지 않은 것과 그 조건, 확인·변경 명령, 함정 셋을 적었다. decision-24와 doc-19에 상호 링크를 걸었다. Task-Id: GF-137 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: 9178237 Tool-Calls: 5 Hooks-Commit: 8685bbf Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...4-cherry-pick-\352\270\210\354\247\200.md" | 3 + ...1\353\262\225-\354\213\244\354\270\241.md" | 7 +- ...5\354\206\214-\354\204\244\354\240\225.md" | 108 ++++++++++++++++++ ...25\354\240\234\355\225\234\353\213\244.md" | 3 +- 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 "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" 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" index 09a21ec..d6ce8ab 100644 --- "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" @@ -106,6 +106,9 @@ rebase-merge로 병합하는 관행이 있었다. **켜지 않은 것**: `required_signatures`(서명 강제). 현재 이 저장소의 커밋은 `%G?`가 `N`으로 나오므로 켜면 자기 push가 막힌다. 서명 설정을 먼저 정리한 뒤 검토할 사항이다. +원격 설정의 현재 상태, 확인·변경 명령, 함정은 **doc-20(GitHub 원격 강제 설정 관리)** 에 +정리했다. 연산별 판별 방법과 우회 경로 전수 조사는 doc-19다. + ## Consequences - 트레일러가 자기 커밋에 대해 참인 상태가 유지된다. `Signed-off-by`가 실제 커미터와, 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" index 0a59344..f833431 100644 --- "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" @@ -3,7 +3,7 @@ id: doc-19 title: 이력을 바꾸는 git 연산별 훅 판별 방법 (실측) type: specification created_date: '2026-09-26 08:29' -updated_date: '2026-09-26 12:46' +updated_date: '2026-09-26 12:55' --- # 이력을 바꾸는 git 연산별 훅 판별 방법 @@ -194,3 +194,8 @@ amend 거부를 `source=commit`만으로 하면 **`rebase -i`가 깨진다.** `r 로컬 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..b5d5e31 --- /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-09-26 12:54' +--- +# 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-format \ + --jq '{allow_merge_commit, allow_rebase_merge, allow_squash_merge}' + +# ruleset 목록 +gh api repos/coralstay/git-format/rulesets \ + --jq '.[] | "\(.name) | \(.enforcement) | id=\(.id)"' + +# 특정 ruleset 상세 +gh api repos/coralstay/git-format/rulesets/23313297 \ + --jq '{name, enforcement, include: .conditions.ref_name.include, rules: [.rules[].type]}' +``` + +## 변경 방법 + +```sh +# 병합 방식 변경 +gh api -X PATCH repos/coralstay/git-format \ + -f allow_merge_commit=true -F allow_rebase_merge=false -F allow_squash_merge=false + +# ruleset 갱신 (PUT은 전체 교체다 - 기존 규칙을 빠뜨리면 사라진다) +gh api -X PUT repos/coralstay/git-format/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/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" index aa80e3d..5824abd 100644 --- "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" @@ -4,7 +4,7 @@ title: append-only 이력 정책을 훅으로 강제한다 status: Draft assignee: [] created_date: '2026-09-26 08:30' -updated_date: '2026-09-26 12:46' +updated_date: '2026-09-26 12:55' labels: - hooks - policy @@ -15,6 +15,7 @@ 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 --- From 0e05beded03d097c524aec821ee3a03368aaf43d Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 22:22:25 +0900 Subject: [PATCH 05/20] =?UTF-8?q?[docs][backlog]=20=EC=9B=90=EA=B2=A9=20?= =?UTF-8?q?=EC=AA=BD=20=EB=82=A8=EC=9D=80=20=EA=B5=AC=EB=A9=8D=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=ED=94=84=ED=8A=B8=202=EA=B1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit force push만 막는 것으로는 부족하다. rulesets에 커밋 메시지 패턴 규칙이 아예 없어서 원격은 트레일러가 참인지 검사할 수단이 없다. DRAFT-19: 트레일러 정합성을 CI에서 검사한다. 유저가 제안한 "트레일러를 뜯어 정합성 최종 검사"의 자리가 여기다 - post-commit은 커밋 후라 막을 수 없고, CI는 완성된 객체를 보면서 required status check으로 병합을 막을 수 있는 유일한 자리다. Signed-off-by와 실제 committer 불일치가 이력 재작성의 증거가 된다. DRAFT-20: 보호 범위 확대 검토. task 브랜치가 보호되지 않아 force push로 다시 쓸 수 있고, 그 상태로 merge되면 main은 append-only인데 작업 기록은 이미 위조됐다. PR 필수, 서명, 태그, 저장소 설정 되돌림 감지도 함께. 둘 다 아직 태스크가 아니다. Task-Id: GF-137 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: 44727008 Tool-Calls: 26 Hooks-Commit: 59590de Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...244-\342\200\224-required-status-check.md" | 57 +++++++++++++++++++ ...4\353\252\205-\355\203\234\352\267\270.md" | 50 ++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 "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" create mode 100644 "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" 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..89f8b8b --- /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,57 @@ +--- +id: DRAFT-19 +title: 트레일러 정합성을 CI에서 검사한다 — required status check +status: Draft +assignee: [] +created_date: '2026-09-26 13:22' +updated_date: '2026-09-26 13:22' +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은 통과하는지 확인 + 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 각 항목에 대해 켠다/안 켠다와 그 근거가 기록됐다 + From e97b4e8b971eecaafa12b3f5c9198477a7db3682 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 26 Sep 2026 22:24:35 +0900 Subject: [PATCH 06/20] =?UTF-8?q?[docs][backlog]=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=95=C2=B7=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EC=9D=84=20=EB=93=9C=EB=9E=98=ED=94=84=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=84=B8=EC=9A=B4=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이 브랜치의 커밋들이 GF-137을 참조하는데 그 태스크가 존재하지 않았다. append-only라 커밋을 고치지 않고 번호를 실재하게 만든다. 브랜치의 실제 작업은 정책 결정과 문서화, 원격 강제 적용이다. 훅 구현은 DRAFT-18이 맡는다. Task-Id: GF-137 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: 5653750 Tool-Calls: 2 Hooks-Commit: 1a6200f Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...5\354\240\234-\354\240\201\354\232\251.md" | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 "backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" diff --git "a/backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" "b/backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" new file mode 100644 index 0000000..919be36 --- /dev/null +++ "b/backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" @@ -0,0 +1,46 @@ +--- +id: DRAFT-21 +title: 'append-only 이력 정책 결정과 문서화, 원격 강제 적용' +status: Draft +assignee: [] +created_date: '2026-09-26 13:24' +updated_date: '2026-09-26 13:24' +labels: + - policy + - docs +dependencies: [] +references: + - decision-24 +documentation: + - backlog/docs/doc-19 - 이력을-바꾸는-git-연산별-훅-판별-방법-실측.md + - backlog/docs/doc-20 - GitHub-원격-강제-설정-관리-—-rulesets와-저장소-설정.md +priority: high +type: docs +--- + +## Description + + +이력 재작성에 대한 정책이 없었고, 오히려 PR을 rebase-merge로 병합하는 관행이 있었다. 그 관행이 실제로 기록을 거짓으로 만들고 있었다 — 실측: main의 커밋들이 Signed-off-by: cpu-once를 달고 있는데 실제 committer는 coralstay이고 %G?가 전부 N이다. + +이 태스크는 정책을 결정하고 문서화하고, 원격 쪽 강제를 적용한다. 훅 구현은 DRAFT-18이 맡는다. + +전제는 README에 적힌 "에이전트가 프로그래밍하고 git이 그 작업의 로그로 동작한다"다. 로그라면 append-only가 맞다. + + +## Acceptance Criteria + +- [ ] #1 append-only 이력 정책을 decision으로 기록한다 — 연산별 허용/거부와 금지선(공개된 이력) +- [ ] #2 이력을 바꾸는 git 연산별 훅 판별 방법을 실측해 문서로 남긴다 +- [ ] #3 우회 경로를 전수 조사해 문서에 남긴다 — 훅이 없는 경로, 판별 불가 경로, 로컬이 닿지 않는 경로, 오탐 +- [ ] #4 GitHub 원격 강제를 적용한다 — 병합 방식을 머지 커밋만 남기고 force push와 브랜치 삭제를 막는다 +- [ ] #5 원격 설정 관리 문서를 만든다 — 현재 상태, 켜면 안 되는 규칙, 확인·변경 명령, 함정 +- [ ] #6 claude-rails의 fast-forward 전용 강제 훅이 정책과 충돌하는 점을 그쪽 저장소에 기록한다 +- [ ] #7 후속 작업을 드래프트로 남긴다 — 로컬 훅 강제, CI 트레일러 정합성 검사, 원격 보호 범위 확대 +- [ ] #8 기존 rebase-merge 지시가 폐기됐음을 메모리에 반영한다 + + +## Definition of Done + +- [ ] #1 실제로 머지 커밋으로 병합이 되는지 확인 + From 7fef01ec70a86991da0d2b633c8b4b6f88dd9526 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:56:39 +0900 Subject: [PATCH 07/20] =?UTF-8?q?[docs][backlog]=20DRAFT-21=EC=9D=84=20GF-?= =?UTF-8?q?137=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-137 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 0 Tool-Calls: 0 Hooks-Commit: d873f64 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename "backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" => "backlog/tasks/gf-137 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" (98%) diff --git "a/backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" "b/backlog/tasks/gf-137 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" similarity index 98% rename from "backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" rename to "backlog/tasks/gf-137 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" index 919be36..26ebb52 100644 --- "a/backlog/drafts/draft-21 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" +++ "b/backlog/tasks/gf-137 - append-only-\354\235\264\353\240\245-\354\240\225\354\261\205-\352\262\260\354\240\225\352\263\274-\353\254\270\354\204\234\355\231\224-\354\233\220\352\262\251-\352\260\225\354\240\234-\354\240\201\354\232\251.md" @@ -1,7 +1,7 @@ --- -id: DRAFT-21 +id: GF-137 title: 'append-only 이력 정책 결정과 문서화, 원격 강제 적용' -status: Draft +status: To Do assignee: [] created_date: '2026-09-26 13:24' updated_date: '2026-09-26 13:24' From 574859c8f08806c45acfcc3704fc3771d94ed24b Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:56:39 +0900 Subject: [PATCH 08/20] =?UTF-8?q?[docs][backlog]=20=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EA=B7=B8=EB=9E=A8=20=EB=B9=8C=EB=93=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=B4=ED=94=84=EB=9D=BC=EC=9D=B8=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=ED=94=84=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-137 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 0 Tool-Calls: 0 Hooks-Commit: 9e89516 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...4-\354\204\270\354\232\264\353\213\244.md" | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 "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" 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은 마크다운 안의 인라인 /로 참조하면 렌더되지만 그 SVG는 바깥 문서의 CSS 변수를 못 받는다. 실측해서 정하고 근거를 남긴다. + +제약: graphviz 의존은 개발·CI 전용이어야 한다. decision-23에서 언어별 lint를 들어낸 이유가 컨슈머에게 도구 존재를 전제하게 만든다는 것이었다 — install.sh와 훅 실행 경로에는 graphviz가 새면 안 된다. + + +## Acceptance Criteria + +- [ ] #1 diagrams/*.dot 6개(lifecycle, hook1-prepare, hook2-commitmsg, hook3-postcommit, marker, token-cursor)를 저장소에 들인다 +- [ ] #2 build-diagrams.py를 추가한다 — DOT→SVG 렌더, 센티넬 hex→CSS 변수 치환, 마커 교체 +- [ ] #3 주입 대상을 실측해 결정하고 근거를 남긴다 — GitHub 마크다운의 svg/style sanitize 동작과 CSS 변수 상속 여부 +- [ ] #4 렌더 산출물이 .dot과 어긋나면 실패하는 검사를 테스트에 추가한다 +- [ ] #5 graphviz 의존이 install.sh와 훅 실행 경로에 새지 않음을 확인한다(decision-23의 교훈) +- [ ] #6 diagrams/readme.md에 '원본은 .dot, SVG는 생성물' 규약과 센티넬 색 대응표를 기록한다 + + +## Definition of Done + +- [ ] #1 .dot 하나를 고치고 빌드를 안 돌린 상태에서 AC#4의 검사가 실제로 실패하는 것을 확인한다 +- [ ] #2 CI가 통과한다 + From 318a958887f852bb05038f0e9e941f0650135e15 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:57:31 +0900 Subject: [PATCH 09/20] =?UTF-8?q?[docs][backlog]=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EC=9E=AC=EC=A0=95=EC=9D=98=C2=B7git-logbook=20=EA=B0=9C?= =?UTF-8?q?=EB=AA=85=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 Task-Id: GF-137 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1252972 Tool-Calls: 6 Hooks-Commit: c7ab8ab Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...34\353\252\205\355\225\234\353\213\244.md" | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 "backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" diff --git "a/backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" "b/backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" new file mode 100644 index 0000000..5d647c3 --- /dev/null +++ "b/backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" @@ -0,0 +1,50 @@ +--- +id: DRAFT-22 +title: 프로젝트 목적을 재정의하고 git-logbook으로 개명한다 +status: Draft +assignee: [] +created_date: '2026-10-03 02:57' +updated_date: '2026-10-03 02:57' +labels: + - docs + - naming +dependencies: [] +priority: high +type: docs +--- + +## Description + + +README와 도구 이름은 이 프로젝트를 '커밋 메시지 형식을 정하는 도구'로 소개한다. 그런데 실제 설계는 그보다 넓다 — append-only 이력 정책과 GitHub 원격 강제(decision-24, DRAFT-18/19/20), Tokens-Used·AI-Model 같은 출처 측정 트레일러와 게이트, Task-Id 브랜치 강제, 에디터 커밋 금지. 2026-10-03 범위 점검에서 이 어긋남이 드러났고, 유저가 판단했다: 이것들까지 포함하는 것이 이 프로젝트의 목적이다. + +그래서 목적을 세 기둥으로 다시 적는다 — 형식(제목·본문 규칙), 출처 기록(트레일러 자동 기록과 관련 게이트), 이력 불변(append-only를 로컬 훅과 원격으로 강제). 이 목적은 decision-11(서버측·CI 검증은 범위 밖)과 decision-12(push 단계는 다루지 않음)의 범위 제외 조항과 충돌하므로, 새 decision이 그 조항을 대체한다고 명시해야 한다. + +이름도 목적에 맞춘다. git-format은 첫 번째 기둥만 말한다. git-logbook(항해일지 — 정해진 양식으로 순서대로 쓰고 지난 장은 고치지 않는다)이 세 기둥을 함께 담는다. GitHub 저장소는 새로 만들지 않고 coralstay/git-format을 rename한다 — 이력·PR·rulesets가 그대로 남고 예전 URL은 GitHub이 리다이렉트한다. + +이번에 바꾸지 않는 것과 이유: +- 설정 키·파일명(gitformat.*, gitformat.conf, .gitformat-verified, _GITFORMAT_AMEND_GUARD): 컨슈머의 git config를 깨는 호환성 변경이라 별건이다. gitformat.conf 개칭을 이미 다루는 GF-133으로 넘긴다. +- 태스크 접두어 GF: 바꾸면 기존 태스크·브랜치·커밋의 Task-Id와 어긋난다. 지난 기록을 다시 쓰는 것은 append-only와 반대다. +- backlog의 지난 decision·doc·task 본문: 당시의 기록이다. +- 로컬 클론 디렉터리 이름: 각자의 환경 문제다. + + +## Acceptance Criteria + +- [ ] #1 프로젝트 목적(형식·출처 기록·이력 불변)을 decision-25로 기록하고, decision-11/12의 범위 제외 조항을 대체한다고 명시한다 +- [ ] #2 decision-25가 decision-24, DRAFT-18/19/20, decision-22, 토큰 측정, AI-Model 게이트, Task-Id 강제, 에디터 금지를 목적 안의 것으로 명시한다 +- [ ] #3 README의 제목·소개·다루는 범위 섹션이 새 목적과 git-logbook 이름으로 바뀐다 +- [ ] #4 README의 clone URL이 실제 원격 coralstay/git-logbook을 가리킨다 +- [ ] #5 install.sh 출력·주석, hooks 주석, .gitmessage, CI 주석, tests의 표시 이름 git-format이 git-logbook으로 바뀐다 (설정 키·파일명 gitformat.*은 제외) +- [ ] #6 backlog/config.yml의 project_name이 git-logbook으로 바뀐다 +- [ ] #7 GF-133에 gitformat.* 설정 키와 마커 파일명을 새 이름으로 옮기는 AC가 추가된다 +- [ ] #8 PR을 머지 커밋으로 병합한 뒤 GitHub 저장소를 coralstay/git-logbook으로 rename하고 로컬 origin URL을 갱신한다 + + +## Definition of Done + +- [ ] #1 python3 -m unittest discover -s tests 전체 통과 +- [ ] #2 ruff check 통과 +- [ ] #3 git grep -i git-format -- ':!backlog' 결과에 의도한 예외만 남는다 +- [ ] #4 rename 뒤 git ls-remote origin 성공, 예전 URL 리다이렉트 확인 + From 8f9148d3f23647faff053c5ab07cff80f346320e Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:58:29 +0900 Subject: [PATCH 10/20] =?UTF-8?q?[docs][backlog]=20DRAFT-22=EB=A5=BC=20GF-?= =?UTF-8?q?138=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-137 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1017085 Tool-Calls: 3 Hooks-Commit: 20109cd Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...2\260\234\353\252\205\355\225\234\353\213\244.md" | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) rename "backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" => "backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" (94%) diff --git "a/backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" similarity index 94% rename from "backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" rename to "backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" index 5d647c3..885d211 100644 --- "a/backlog/drafts/draft-22 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" @@ -1,14 +1,20 @@ --- -id: DRAFT-22 +id: GF-138 title: 프로젝트 목적을 재정의하고 git-logbook으로 개명한다 -status: Draft +status: To Do assignee: [] created_date: '2026-10-03 02:57' -updated_date: '2026-10-03 02:57' +updated_date: '2026-10-03 02:58' labels: - docs - naming dependencies: [] +references: + - decision-11 + - decision-12 + - decision-24 +documentation: + - backlog/docs/doc-20 - GitHub-원격-강제-설정-관리-—-rulesets와-저장소-설정.md priority: high type: docs --- From e206c24f58f20f14e7944f9707972b36cac54cb3 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:59:29 +0900 Subject: [PATCH 11/20] =?UTF-8?q?[docs][backlog]=20decision-25=20=EB=AA=A9?= =?UTF-8?q?=EC=A0=81=20=EC=9E=AC=EC=A0=95=EC=9D=98=EC=99=80=20=EA=B0=9C?= =?UTF-8?q?=EB=AA=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1795920 Tool-Calls: 7 Hooks-Commit: d11bd25 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...0\355\225\255-\353\214\200\354\262\264.md" | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 "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" 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..5ae892a --- /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,67 @@ +--- +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된 이력을 다시 쓰는 rebase를 + 막는 `pre-rebase`(DRAFT-18)가 들어온다. +- 두 decision의 나머지 판단은 유지한다. pre-push에서 테스트·빌드를 돌리지 않는다는 것, + 컨슈머용 재사용 워크플로를 배포하지 않았던 당시 판단은 그대로다. + +decision-23의 언어별 lint 제거는 유지한다. 언어 lint는 세 기둥 어디에도 속하지 않는다. +대체되는 것은 그 제목의 "커밋 형식만"이라는 범위 표현뿐이다. + +**이름을 git-logbook으로 바꾼다.** git-format은 첫 번째 기둥만 말한다. 항해일지(logbook)는 +정해진 양식으로, 순서대로 쓰고, 지난 장은 고치지 않는다 — 세 기둥이 한 단어에 담긴다. +GitHub 저장소는 새로 만들지 않고 rename한다(이력·PR·rulesets 유지, 예전 URL 리다이렉트). + +## Consequences + +- README의 소개와 "다루는 범위"를 세 기둥으로 다시 쓴다. +- 이번에 바꾸지 않는 이름이 있다. + - 설정 키·파일명(`gitformat.*`, `gitformat.conf`, `.gitformat-verified`, + `_GITFORMAT_AMEND_GUARD`): 컨슈머의 git config를 깨는 호환성 변경이라 GF-133에서 다룬다. + - 태스크 접두어 `GF`: 바꾸면 기존 커밋의 `Task-Id`와 어긋난다. 지난 기록을 고치는 것은 + 이력 불변과 반대다. + - 지난 decision·doc·task 본문의 "git-format": 당시의 기록이라 그대로 둔다. +- 범위 판단 기준이 생긴다. 새 기능은 세 기둥 중 어디에 속하는지로 판단한다. 어디에도 + 속하지 않으면(decision-23의 언어 lint처럼) 범위 밖이다. +- 원격 강제가 범위에 들어오면서 GitHub 의존이 생긴다. decision-11이 피하려던 벤더 결합이다. + 로컬 훅이 1차 강제이고 원격은 로컬이 닿지 않는 곳만 막는다는 계층 구분(decision-24)으로 + 결합을 최소로 둔다. From ca65ac49e8203e0fe3ed1cd04f6fee88ccd5c62d Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:00:08 +0900 Subject: [PATCH 12/20] =?UTF-8?q?[docs][readme]=20=EB=AA=A9=EC=A0=81?= =?UTF-8?q?=EC=9D=84=20=EC=84=B8=20=EA=B8=B0=EB=91=A5=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=93=B0=EA=B3=A0=20=EA=B0=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1252467 Tool-Calls: 4 Hooks-Commit: 8fce8d1 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- README.md | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1136947..7b1f82a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@
-# 🧬 git-format +# 📒 git-logbook -**git 자체 기능만으로 동작하는 커밋 메시지 규칙 · 검증 · 이력 정형화 도구** +**git 이력을 작업 로그로 만드는 도구 — 커밋 형식 · 출처 기록 · 이력 불변** [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![Hooks: Python 3](https://img.shields.io/badge/hooks-Python%203-3776ab.svg)](./hooks/) [![Installer: POSIX sh](https://img.shields.io/badge/installer-POSIX%20sh-89e051.svg)](./install.sh) [![Requires: git + python3](https://img.shields.io/badge/requires-git%20%2B%20python3-brightgreen.svg)](#-필요조건을-말씀드립니다) -`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,25 @@ 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). + +| 기둥 | 하는 일 | 지금 있는 것 | 계획 | +| --- | --- | --- | --- | +| **형식** | 사람이 쓰는 제목·본문을 정해진 양식으로 | 제목·본문 검증, 에디터 경로 거부 | 검증을 `prepare-commit-msg`로 통합(GF-127) | +| **출처 기록** | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남김 | 위 트레일러 전체, `Task-Id` 브랜치 강제, `AI-Model` 게이트 | 측정 재작성(GF-129), 트레일러 집합 재정의(decision-19) | +| **이력 불변** | 한 번 남긴 기록은 다시 쓰지 않음(append-only, decision-24) | GitHub 원격 설정: 머지 커밋만 허용, force push·브랜치 삭제 금지(doc-20) | 로컬 훅 강제(DRAFT-18), CI 트레일러 검사(DRAFT-19) | + +세 기둥은 서로를 필요로 합니다. 이력이 다시 쓰이면 트레일러가 자기 커밋에 대해 거짓이 +되고, 기록할 내용이 정형화돼 있지 않으면 지킬 가치가 줄어듭니다. 로컬 훅은 +`--no-verify`나 웹 UI 커밋을 막지 못하므로, 그 부분은 원격 설정이 맡습니다. + +**언어별 lint는 다루지 않습니다**(decision-23) — 세 기둥 어디에도 속하지 않고, 컨슈머에게 +npm/ruff/clang-format 같은 도구를 전제하게 만듭니다. 언어 검사가 필요하면 CI에서 돌리시기 +바랍니다. git-logbook이 `core.hooksPath`를 점유하므로 그 저장소의 `.git/hooks/*`는 +무시되고, 자기 훅과 함께 쓰는 방법은 아직 정하지 않았습니다. + +> 이름을 git-format에서 git-logbook으로 바꿨습니다(decision-25). 설정 키와 파일명 +> (`gitformat.*`, `gitformat.conf`)은 기존 설치를 깨지 않도록 아직 예전 이름입니다(GF-133). 구성은 Python 훅 + 설정 파일 하나(`hooks/gitformat.conf`) + POSIX sh 설치 스크립트(`install.sh`)입니다. `core.hooksPath` · `init.templateDir` · @@ -135,17 +150,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`을 실행할 때마다 훅과 커밋 템플릿이 자동으로 심어집니다 From 6d37e36e958346a15ed35987e1a75ff90a69c355 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:01:37 +0900 Subject: [PATCH 13/20] =?UTF-8?q?[chore]=20=ED=91=9C=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9D=84=20git-logbook=EC=9C=BC=EB=A1=9C=20=EB=B0=94?= =?UTF-8?q?=EA=BE=BC=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1467624 Tool-Calls: 5 Hooks-Commit: f401596 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- .gitmessage | 2 +- backlog/config.yml | 2 +- hooks/gitformat.conf | 4 ++-- hooks/post-commit | 4 ++-- hooks/prepare-commit-msg | 6 +++--- install.sh | 26 +++++++++++++------------- tests/test_end_to_end_commit.py | 4 ++-- tests/test_install_script.py | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) 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/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/hooks/gitformat.conf b/hooks/gitformat.conf index b77b1de..fde85fc 100644 --- a/hooks/gitformat.conf +++ b/hooks/gitformat.conf @@ -1,9 +1,9 @@ -# git-format 내부 기본값 상수 파일(git config 포맷). 훅 3개(prepare-commit-msg, +# git-logbook 내부 기본값 상수 파일(git config 포맷). 훅 3개(prepare-commit-msg, # commit-msg, post-commit)가 이 파일을 `git config --file`로 읽어 값을 가져온다 — # 각 훅의 실행 로직(함수/제어흐름)은 이 파일과 무관하게 파일마다 독립적으로 # 유지된다(공유하는 건 값뿐). # -# 이 파일은 git-format 자체의 "내부 기본값"이고, 컨슈머 저장소가 오버라이드하는 +# 이 파일은 git-logbook 자체의 "내부 기본값"이고, 컨슈머 저장소가 오버라이드하는 # `git config gitformat.taskPrefix`/`gitformat.branchExempt`/`gitformat.aiModel` # (컨슈머 저장소의 로컬/전역 git config)과는 레이어가 다르다 — 오버라이드가 있으면 # 그 값이 우선하고, 없으면 여기 기본값을 쓴다. diff --git a/hooks/post-commit b/hooks/post-commit index 2201619..f406e36 100755 --- a/hooks/post-commit +++ b/hooks/post-commit @@ -459,9 +459,9 @@ def trailer_tokens_used(): queue_trailer(TRAILER_TOOL_CALLS, calls_value) -# Hooks-Commit(decision-5): 이 커밋을 검증한 git-format 훅 코드 자체의 커밋 해시. +# Hooks-Commit(decision-5): 이 커밋을 검증한 git-logbook 훅 코드 자체의 커밋 해시. # 수동 버전 문자열 대신 훅 스크립트가 위치한 저장소의 HEAD를 그대로 읽으므로 항상 -# 정확하다. hooks/가 git-format 클론 안에 있지 않으면(예: 훅만 복사해 배포한 경우) +# 정확하다. hooks/가 git-logbook 클론 안에 있지 않으면(예: 훅만 복사해 배포한 경우) # 조회가 실패하는데, 그때는 커밋을 막지 않고 이 트레일러만 생략하는 게 의도된 # 동작이다 - 이 함수의 판단이 rev-parse 성공 여부에 달려 있으므로 예외가 아니라 # 종료 코드로 좁게 확인한다. diff --git a/hooks/prepare-commit-msg b/hooks/prepare-commit-msg index 2878f10..424784c 100755 --- a/hooks/prepare-commit-msg +++ b/hooks/prepare-commit-msg @@ -5,8 +5,8 @@ # 옮겨온다. 그때까지 commit-msg/post-commit이 그대로 자기 일을 계속한다. # # GF-126에서 언어별 lint를 pre-commit에서 이 파일로 옮겼다가 GF-135에서 그 lint를 -# 통째로 지웠다 — git-format의 범위는 커밋 메시지 형식과 트레일러이고, 언어 lint는 -# 결이 다른 기능이었다(decision-23). 남은 것은 검증마커 기록뿐이고 그 이유는 +# 통째로 지웠다 — 언어 lint는 git-logbook의 범위(형식·출처 기록·이력 불변, decision-25) +# 어디에도 속하지 않는 기능이었다(decision-23). 남은 것은 검증마커 기록뿐이고 그 이유는 # write_verified_marker()의 주석에 있다. # # 이 훅을 본체로 삼는 근거(실측 절차와 결과는 doc-15): prepare-commit-msg는 @@ -18,7 +18,7 @@ # 에디터 경로 판정에 $1의 내용을 쓴다 — $2로 에디터를 판정할 수 없는 이유는 아래 # has_author_message()의 주석에 적었다. # -# template/(GF-9)로 설치된 저장소는 .git/hooks/prepare-commit-msg 자체가 git-format +# template/(GF-9)로 설치된 저장소는 .git/hooks/prepare-commit-msg 자체가 git-logbook # 클론을 가리키는 심볼릭 링크다. 링크가 놓인 .git/hooks/가 아니라 링크가 가리키는 # 실제 위치를 기준으로 gitformat.conf를 찾아야 한다(GF-16) — realpath가 링크 체인을 # 따라간다. diff --git a/install.sh b/install.sh index 3d89b99..ad5c73a 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,10 @@ #!/bin/sh -# git-format 설치 스크립트: 기존 저장소에 core.hooksPath와 commit.template을 설정하고 +# git-logbook 설치 스크립트: 기존 저장소에 core.hooksPath와 commit.template을 설정하고 # (decision-2), 훅 실행에 필요한 python3 존재를 확인한다(decision-16). 이 스크립트가 -# 위치한 git-format 클론 자체를 훅 소스로 사용한다. +# 위치한 git-logbook 클론 자체를 훅 소스로 사용한다. # # 사용법: -# /path/to/git-format/install.sh [target-repo-dir] +# /path/to/git-logbook/install.sh [target-repo-dir] # target-repo-dir을 생략하면 현재 디렉터리를 대상으로 한다. set -eu @@ -30,7 +30,7 @@ fi # 훅은 Python으로 실행된다(decision-16). 여기서 막지 않으면 설정은 다 끝난 # 뒤 첫 커밋에서야 훅이 실행 실패로 깨져 원인을 찾기 어렵다. if ! command -v python3 >/dev/null 2>&1; then - echo "install.sh: python3을 찾을 수 없습니다 - git-format 훅은 Python으로 실행됩니다." >&2 + echo "install.sh: python3을 찾을 수 없습니다 - git-logbook 훅은 Python으로 실행됩니다." >&2 exit 1 fi @@ -56,7 +56,7 @@ fi git -C "$TARGET" config core.hooksPath "$HOOKS_DIR" git -C "$TARGET" config commit.template "$GITMESSAGE" -echo "git-format: ${TARGET} 설정 완료" +echo "git-logbook: ${TARGET} 설정 완료" echo " core.hooksPath = ${HOOKS_DIR}" echo " commit.template = ${GITMESSAGE}" @@ -88,10 +88,10 @@ case "$GLOBAL_MODE" in sync_template git config --global init.templateDir "$TEMPLATE_DIR" git config --global commit.template "$GITMESSAGE" - echo "git-format: 전역 init.templateDir/commit.template 설정 완료 (앞으로 만드는 새 저장소에 자동 적용)" + echo "git-logbook: 전역 init.templateDir/commit.template 설정 완료 (앞으로 만드는 새 저장소에 자동 적용)" ;; no) - echo "git-format: 전역 설정은 건너뜁니다." + echo "git-logbook: 전역 설정은 건너뜁니다." ;; ask) if [ -t 0 ]; then @@ -102,18 +102,18 @@ case "$GLOBAL_MODE" in sync_template git config --global init.templateDir "$TEMPLATE_DIR" git config --global commit.template "$GITMESSAGE" - echo "git-format: 전역 init.templateDir/commit.template 설정 완료." + echo "git-logbook: 전역 init.templateDir/commit.template 설정 완료." ;; *) - echo "git-format: 전역 설정은 건너뜁니다. 나중에: ${SELF_DIR}/install.sh --global" + echo "git-logbook: 전역 설정은 건너뜁니다. 나중에: ${SELF_DIR}/install.sh --global" ;; esac else - echo "git-format: 비대화형 환경이라 전역 설정은 건너뜁니다. --global로 자동 적용 가능." + echo "git-logbook: 비대화형 환경이라 전역 설정은 건너뜁니다. --global로 자동 적용 가능." fi ;; esac -echo "git-format: 참고 - git push --no-verify는 로컬 훅으로 탐지할 수 없습니다." -echo " push 단계까지 막는 서버사이드 백스톱은 git-format 범위 밖입니다 - 필요하면" -echo " 직접 구성하세요 (README의 '--no-verify 우회 탐지' 참고)." +echo "git-logbook: 참고 - git push --no-verify는 로컬 훅으로 탐지할 수 없습니다." +echo " push된 이력은 원격 저장소 설정이 지킵니다 - 이 저장소에 맞게 직접 구성하세요" +echo " (README의 '다루는 범위' 참고)." diff --git a/tests/test_end_to_end_commit.py b/tests/test_end_to_end_commit.py index 70839ed..8688480 100644 --- a/tests/test_end_to_end_commit.py +++ b/tests/test_end_to_end_commit.py @@ -26,7 +26,7 @@ def test_언어_도구가_거부할_파일도_커밋이_통과한다(self): """[GF-135] 언어별 검사가 제거돼 ruff가 거부할 파일도 커밋을 막지 않는다 GF-135 전에는 pyproject.toml이 있는 저장소의 `import os`(F401)가 - checks/python.py의 ruff에 걸려 커밋이 막혔다. 지금은 git-format이 언어 + checks/python.py의 ruff에 걸려 커밋이 막혔다. 지금은 git-logbook이 언어 도구를 전혀 실행하지 않는다(decision-23) — 이 단언이 없으면 lint가 슬그머니 돌아와도 스위트가 똑같이 통과한다. """ @@ -63,7 +63,7 @@ def test_template_심볼릭_링크_설치에서도_훅이_자기_설정을_찾 new_repo = self.temp_dir() self.make_repo(path=new_repo, hooks_path=None, env=env) - # init.templateDir이 .git/hooks/*를 git-format 클론을 가리키는 심볼릭 링크로 + # init.templateDir이 .git/hooks/*를 git-logbook 클론을 가리키는 심볼릭 링크로 # 채웠는지부터 확인한다 — 이게 아니면 GF-16 시나리오 자체가 재현 안 된다. linked_hook = new_repo / ".git" / "hooks" / "prepare-commit-msg" self.assertTrue( diff --git a/tests/test_install_script.py b/tests/test_install_script.py index 9647e0f..e652708 100644 --- a/tests/test_install_script.py +++ b/tests/test_install_script.py @@ -27,7 +27,7 @@ def setUp(self): self.run_cmd_ok(["git", "init", "-q"], cwd=self.repo) def fake_root(self): - """install.sh와 hooks/만 들어 있는 git-format 클론 사본. + """install.sh와 hooks/만 들어 있는 git-logbook 클론 사본. 실제 hooks/ 파일을 지우는 테스트는 이 저장소 자신이 아니라 이 사본에서 한다. """ From 8462dd0364c6b5aafce440819f65048213573d49 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:01:37 +0900 Subject: [PATCH 14/20] =?UTF-8?q?[docs][backlog]=20GF-138=20=EC=B0=A9?= =?UTF-8?q?=EC=88=98=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 0 Tool-Calls: 0 Hooks-Commit: 61f0eac Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...0\234\353\252\205\355\225\234\353\213\244.md" | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" index 885d211..c2252c5 100644 --- "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" @@ -1,8 +1,9 @@ --- id: GF-138 title: 프로젝트 목적을 재정의하고 git-logbook으로 개명한다 -status: To Do -assignee: [] +status: In Progress +assignee: + - '@claude' created_date: '2026-10-03 02:57' updated_date: '2026-10-03 02:58' labels: @@ -54,3 +55,14 @@ README와 도구 이름은 이 프로젝트를 '커밋 메시지 형식을 정 - [ ] #3 git grep -i git-format -- ':!backlog' 결과에 의도한 예외만 남는다 - [ ] #4 rename 뒤 git ls-remote origin 성공, 예전 URL 리다이렉트 확인 + +## Implementation Plan + + +1. decision-25 기록 (AC#1,#2) → 커밋 +2. README 제목·소개·범위·clone URL 재작성 (AC#3,#4) → 커밋 +3. 나머지 표시 이름 치환: install.sh, hooks 주석, .gitmessage, CI, tests, backlog/config.yml (AC#5,#6) → 테스트 → 커밋 +4. GF-133에 설정 키 이전 AC 추가 (AC#7) → 커밋 +5. 서브에이전트로 diff 독립 검토 +6. PR → CI → 머지 커밋 병합 → gh repo rename → origin URL 갱신 (AC#8) + From 02e6e94ec1c34bc5dd548f15bc4830b9efa15087 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:01:45 +0900 Subject: [PATCH 15/20] =?UTF-8?q?[docs][backlog]=20GF-133=EC=97=90=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=82=A4=20=EC=9D=B4=EC=A0=84=20AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 330458 Tool-Calls: 1 Hooks-Commit: 2638bd7 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git "a/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" "b/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" index 7332f02..f45a60d 100644 --- "a/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" +++ "b/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" @@ -4,7 +4,7 @@ title: 파일·디렉터리 이름을 역할이 드러나게 개칭 status: To Do assignee: [] created_date: '2026-09-25 19:34' -updated_date: '2026-09-26 03:24' +updated_date: '2026-10-03 03:01' labels: - hooks - naming @@ -12,6 +12,7 @@ dependencies: - GF-132 references: - decision-18 + - decision-25 documentation: - backlog/docs/doc-13 - git-format-재설계-계획-—-커밋-규칙을-prepare-commit-msg로-통합.md - backlog/docs/doc-18 - 재설계-작업-순서와-의존성.md @@ -48,6 +49,8 @@ c_cpp.py로 바꾸는 이유는 이 검사가 실제로 *.c까지 담당하기 - [ ] #6 훅이 새 경로로 검사 스크립트와 설정 파일을 찾는다 - [ ] #7 git mv를 사용해 이력에서 이름 변경으로 추적된다 - [ ] #8 심볼릭 링크로 설치된 저장소에서도 새 경로가 해석된다 +- [ ] #9 git config 설정 키 gitformat.*(taskPrefix·branchExempt·aiModel)와 마커·커서 파일명(.gitformat-verified, .gitformat-token-cursor), 환경변수 _GITFORMAT_AMEND_GUARD를 git-logbook 이름으로 옮긴다 — 프로젝트 개명(decision-25)의 후속 +- [ ] #10 기존 gitformat.* 설정 키를 쓰던 컨슈머 저장소가 깨지지 않도록 읽기 호환 또는 이전 안내를 둔다 ## Definition of Done From 990bda46182edcc95641200ffa9432eeac94c3e4 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:05:45 +0900 Subject: [PATCH 16/20] =?UTF-8?q?[docs]=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81:=20=EC=9B=90=EA=B2=A9=20=EB=B3=B4=ED=98=B8=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=EB=A5=BC=20=EC=82=AC=EC=8B=A4=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=81=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 1893921 Tool-Calls: 5 Hooks-Commit: 5f76777 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- README.md | 11 ++++++----- ...241\260\355\225\255-\353\214\200\354\262\264.md" | 13 ++++++++----- install.sh | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7b1f82a..f945d32 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> ## 🪝 커밋 한 번에 훅이 도는 순서 > 공식 문서: [githooks(5)](https://git-scm.com/docs/githooks). git이 제공하는 훅은 28개이고, -> git-logbook의 훅은 지금 커밋 단계만 다룹니다. push된 이력은 원격 쪽 설정이 지킵니다 +> git-logbook의 훅은 지금 커밋 단계만 다룹니다. push된 이력의 보호는 원격 설정이 맡습니다 > ([다루는 범위](#-다루는-범위)). | 순서 | 훅 | 커밋 객체가 있는가 | `--no-verify`로 건너뛰나 | git-logbook이 하는 일 | @@ -100,13 +100,14 @@ git 이력을 작업 로그로 만드는 데 필요한 세 가지입니다(decis | 기둥 | 하는 일 | 지금 있는 것 | 계획 | | --- | --- | --- | --- | -| **형식** | 사람이 쓰는 제목·본문을 정해진 양식으로 | 제목·본문 검증, 에디터 경로 거부 | 검증을 `prepare-commit-msg`로 통합(GF-127) | +| **형식** | 사람이 쓰는 제목·본문을 정해진 양식으로 | 제목·본문 검증, 에디터 경로 거부, 커밋 단위 규칙(decision-22, 강제 없음) | 검증을 `prepare-commit-msg`로 통합(GF-127) | | **출처 기록** | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남김 | 위 트레일러 전체, `Task-Id` 브랜치 강제, `AI-Model` 게이트 | 측정 재작성(GF-129), 트레일러 집합 재정의(decision-19) | -| **이력 불변** | 한 번 남긴 기록은 다시 쓰지 않음(append-only, decision-24) | GitHub 원격 설정: 머지 커밋만 허용, force push·브랜치 삭제 금지(doc-20) | 로컬 훅 강제(DRAFT-18), CI 트레일러 검사(DRAFT-19) | +| **이력 불변** | 한 번 남긴 기록은 다시 쓰지 않음(append-only, decision-24) | 이 저장소의 GitHub 설정: 머지 커밋만 허용, main의 force push·삭제 금지(doc-20). 컨슈머는 doc-20을 참고해 직접 설정 | 로컬 훅 강제(DRAFT-18 append-only 훅 강제), CI 트레일러 검사(DRAFT-19) | 세 기둥은 서로를 필요로 합니다. 이력이 다시 쓰이면 트레일러가 자기 커밋에 대해 거짓이 되고, 기록할 내용이 정형화돼 있지 않으면 지킬 가치가 줄어듭니다. 로컬 훅은 -`--no-verify`나 웹 UI 커밋을 막지 못하므로, 그 부분은 원격 설정이 맡습니다. +`--no-verify`를 막지 못합니다. 그렇게 다시 쓴 이력이 main에 force push되는 것은 원격 +설정이 거부합니다. 웹 UI 커밋과 트레일러 검사는 CI로 할 계획입니다(DRAFT-19). **언어별 lint는 다루지 않습니다**(decision-23) — 세 기둥 어디에도 속하지 않고, 컨슈머에게 npm/ruff/clang-format 같은 도구를 전제하게 만듭니다. 언어 검사가 필요하면 CI에서 돌리시기 @@ -114,7 +115,7 @@ npm/ruff/clang-format 같은 도구를 전제하게 만듭니다. 언어 검사 무시되고, 자기 훅과 함께 쓰는 방법은 아직 정하지 않았습니다. > 이름을 git-format에서 git-logbook으로 바꿨습니다(decision-25). 설정 키와 파일명 -> (`gitformat.*`, `gitformat.conf`)은 기존 설치를 깨지 않도록 아직 예전 이름입니다(GF-133). +> (`gitformat.*`, `gitformat.conf`, `.gitformat-*`)은 기존 설치를 깨지 않도록 아직 예전 이름입니다(GF-133). 구성은 Python 훅 + 설정 파일 하나(`hooks/gitformat.conf`) + POSIX sh 설치 스크립트(`install.sh`)입니다. `core.hooksPath` · `init.templateDir` · 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" index 5ae892a..1ffcb31 100644 --- "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" @@ -39,10 +39,13 @@ decision-24와 DRAFT-19는 decision-11(서버측·CI 검증은 범위 밖), deci - decision-11의 "서버측/CI 기반 검증은 이 프로젝트 범위 밖": 대체한다. 원격 강제는 이력 불변 기둥의 일부다 — 로컬 훅은 `--no-verify`와 웹 UI 커밋을 막지 못하므로 원격 없이는 기둥이 완결되지 않는다(decision-24 "강제 계층"). -- decision-12의 "push 단계는 훅하지 않는다": 대체한다. push된 이력을 다시 쓰는 rebase를 - 막는 `pre-rebase`(DRAFT-18)가 들어온다. -- 두 decision의 나머지 판단은 유지한다. pre-push에서 테스트·빌드를 돌리지 않는다는 것, - 컨슈머용 재사용 워크플로를 배포하지 않았던 당시 판단은 그대로다. +- 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-23의 언어별 lint 제거는 유지한다. 언어 lint는 세 기둥 어디에도 속하지 않는다. 대체되는 것은 그 제목의 "커밋 형식만"이라는 범위 표현뿐이다. @@ -56,7 +59,7 @@ GitHub 저장소는 새로 만들지 않고 rename한다(이력·PR·rulesets - README의 소개와 "다루는 범위"를 세 기둥으로 다시 쓴다. - 이번에 바꾸지 않는 이름이 있다. - 설정 키·파일명(`gitformat.*`, `gitformat.conf`, `.gitformat-verified`, - `_GITFORMAT_AMEND_GUARD`): 컨슈머의 git config를 깨는 호환성 변경이라 GF-133에서 다룬다. + `.gitformat-token-cursor`, `_GITFORMAT_AMEND_GUARD`): 컨슈머의 git config를 깨는 호환성 변경이라 GF-133에서 다룬다. - 태스크 접두어 `GF`: 바꾸면 기존 커밋의 `Task-Id`와 어긋난다. 지난 기록을 고치는 것은 이력 불변과 반대다. - 지난 decision·doc·task 본문의 "git-format": 당시의 기록이라 그대로 둔다. diff --git a/install.sh b/install.sh index ad5c73a..d9344a0 100755 --- a/install.sh +++ b/install.sh @@ -115,5 +115,5 @@ case "$GLOBAL_MODE" in esac echo "git-logbook: 참고 - git push --no-verify는 로컬 훅으로 탐지할 수 없습니다." -echo " push된 이력은 원격 저장소 설정이 지킵니다 - 이 저장소에 맞게 직접 구성하세요" -echo " (README의 '다루는 범위' 참고)." +echo " push된 이력은 원격 설정이 지킵니다 - 대상 저장소의 원격(GitHub rulesets·병합 방식)을" +echo " 직접 구성하세요 (예시: git-logbook 저장소의 backlog doc-20)." From 63b8ee11808cf32167c5eaeaa06f3d8438e2f630 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:05:46 +0900 Subject: [PATCH 17/20] =?UTF-8?q?[docs][backlog]=20GF-133=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=82=A4=20=EC=9D=B4=EC=A0=84=20AC=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EB=AA=85=ED=99=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 0 Tool-Calls: 0 Hooks-Commit: 9a5d93d Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...254\353\202\230\352\262\214-\352\260\234\354\271\255.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" "b/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" index f45a60d..9536d99 100644 --- "a/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" +++ "b/backlog/tasks/gf-133 - \355\214\214\354\235\274\302\267\353\224\224\353\240\211\355\204\260\353\246\254-\354\235\264\353\246\204\354\235\204-\354\227\255\355\225\240\354\235\264-\353\223\234\353\237\254\353\202\230\352\262\214-\352\260\234\354\271\255.md" @@ -4,7 +4,7 @@ title: 파일·디렉터리 이름을 역할이 드러나게 개칭 status: To Do assignee: [] created_date: '2026-09-25 19:34' -updated_date: '2026-10-03 03:01' +updated_date: '2026-10-03 03:04' labels: - hooks - naming @@ -49,8 +49,8 @@ c_cpp.py로 바꾸는 이유는 이 검사가 실제로 *.c까지 담당하기 - [ ] #6 훅이 새 경로로 검사 스크립트와 설정 파일을 찾는다 - [ ] #7 git mv를 사용해 이력에서 이름 변경으로 추적된다 - [ ] #8 심볼릭 링크로 설치된 저장소에서도 새 경로가 해석된다 -- [ ] #9 git config 설정 키 gitformat.*(taskPrefix·branchExempt·aiModel)와 마커·커서 파일명(.gitformat-verified, .gitformat-token-cursor), 환경변수 _GITFORMAT_AMEND_GUARD를 git-logbook 이름으로 옮긴다 — 프로젝트 개명(decision-25)의 후속 -- [ ] #10 기존 gitformat.* 설정 키를 쓰던 컨슈머 저장소가 깨지지 않도록 읽기 호환 또는 이전 안내를 둔다 +- [ ] #9 기존 gitformat.* 설정 키를 쓰던 컨슈머 저장소가 깨지지 않도록 읽기 호환 또는 이전 안내를 둔다 +- [ ] #10 [gitformat] 섹션의 모든 설정 키(컨슈머 오버라이드 taskPrefix·branchExempt·aiModel과 conf 내부 type·trailer.*·markerFile 등 전부), 마커·커서 파일명(.gitformat-verified, .gitformat-token-cursor), 환경변수 _GITFORMAT_AMEND_GUARD를 git-logbook 이름으로 옮긴다 — 프로젝트 개명(decision-25)의 후속 ## Definition of Done From d224659d04cdc5cdb2185665b1e7d5d9ce2b03a3 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:14:57 +0900 Subject: [PATCH 18/20] =?UTF-8?q?[docs]=20Signed-off-by=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=20=EA=B8=B0=EB=A1=9D=ED=95=98=EA=B3=A0=20GF-?= =?UTF-8?q?128=20AC=EB=A5=BC=20=EB=A7=9E=EC=B6=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 3106161 Tool-Calls: 8 Hooks-Commit: 9ddab80 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- README.md | 2 +- ...0\355\225\255-\353\214\200\354\262\264.md" | 8 +++++++ ...244-\342\200\224-required-status-check.md" | 12 ++++++++++- ...5\354\235\204-\354\260\250\353\213\250.md" | 21 ++++++++++++++----- ...34\353\252\205\355\225\234\353\213\244.md" | 4 +++- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f945d32..9ccdf5e 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ git 이력을 작업 로그로 만드는 데 필요한 세 가지입니다(decis | 기둥 | 하는 일 | 지금 있는 것 | 계획 | | --- | --- | --- | --- | | **형식** | 사람이 쓰는 제목·본문을 정해진 양식으로 | 제목·본문 검증, 에디터 경로 거부, 커밋 단위 규칙(decision-22, 강제 없음) | 검증을 `prepare-commit-msg`로 통합(GF-127) | -| **출처 기록** | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남김 | 위 트레일러 전체, `Task-Id` 브랜치 강제, `AI-Model` 게이트 | 측정 재작성(GF-129), 트레일러 집합 재정의(decision-19) | +| **출처 기록** | 누가·어떤 도구와 모델로·얼마를 들여 만들었는지 자동으로 남김 | 위 트레일러 전체, `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) | 세 기둥은 서로를 필요로 합니다. 이력이 다시 쓰이면 트레일러가 자기 커밋에 대해 거짓이 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" index 1ffcb31..8c3e8e9 100644 --- "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" @@ -47,6 +47,14 @@ decision-24와 DRAFT-19는 decision-11(서버측·CI 검증은 범위 밖), deci - **미결**: 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는 세 기둥 어디에도 속하지 않는다. 대체되는 것은 그 제목의 "커밋 형식만"이라는 범위 표현뿐이다. 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" index 89f8b8b..996a08b 100644 --- "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" @@ -4,7 +4,7 @@ title: 트레일러 정합성을 CI에서 검사한다 — required status check status: Draft assignee: [] created_date: '2026-09-26 13:22' -updated_date: '2026-09-26 13:22' +updated_date: '2026-10-03 03:14' labels: - ci - policy @@ -55,3 +55,13 @@ decision-24가 마커를 "지워질 훅의 오판을 막으려 살려둔 죽은 - [ ] #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/tasks/gf-128 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\202\275\354\236\205\354\235\204-prepare-commit-msg\353\241\234-\354\235\264\354\240\204\355\225\230\352\263\240-\355\202\244-\353\213\250\354\234\204-\354\244\221\353\263\265\354\235\204-\354\260\250\353\213\250.md" "b/backlog/tasks/gf-128 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\202\275\354\236\205\354\235\204-prepare-commit-msg\353\241\234-\354\235\264\354\240\204\355\225\230\352\263\240-\355\202\244-\353\213\250\354\234\204-\354\244\221\353\263\265\354\235\204-\354\260\250\353\213\250.md" index 463b896..cf6eaa8 100644 --- "a/backlog/tasks/gf-128 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\202\275\354\236\205\354\235\204-prepare-commit-msg\353\241\234-\354\235\264\354\240\204\355\225\230\352\263\240-\355\202\244-\353\213\250\354\234\204-\354\244\221\353\263\265\354\235\204-\354\260\250\353\213\250.md" +++ "b/backlog/tasks/gf-128 - \355\212\270\353\240\210\354\235\274\353\237\254-\354\202\275\354\236\205\354\235\204-prepare-commit-msg\353\241\234-\354\235\264\354\240\204\355\225\230\352\263\240-\355\202\244-\353\213\250\354\234\204-\354\244\221\353\263\265\354\235\204-\354\260\250\353\213\250.md" @@ -4,7 +4,7 @@ title: 트레일러 삽입을 prepare-commit-msg로 이전하고 키 단위 중 status: To Do assignee: [] created_date: '2026-09-25 19:33' -updated_date: '2026-09-25 21:43' +updated_date: '2026-10-03 03:14' labels: - hooks - trailers @@ -13,6 +13,7 @@ dependencies: references: - decision-19 - decision-18 + - decision-25 documentation: - backlog/docs/doc-13 - git-format-재설계-계획-—-커밋-규칙을-prepare-commit-msg로-통합.md - backlog/docs/doc-14 - 용어-정리-—-턴-트랜스크립트-귀속-마커-구분.md @@ -43,10 +44,10 @@ type: feature - [ ] #4 메시지에 다른 값의 Co-Authored-By가 있으면 훅이 추가하지 않고 원래 값이 보존된다 - [ ] #5 AI-Tool/AI-Tool-Version/AI-Model을 AI-Agent 한 줄(<도구>/<버전> (<모델>))로 합친다 - [ ] #6 AI-Agent는 구성요소를 못 구해도 줄을 남긴다 (version-unavailable / model-unavailable) -- [ ] #7 Signed-off-by와 Verify-Bypassed 트레일러를 더 이상 삽입하지 않는다 -- [ ] #8 재귀 가드가 필요 없어져 제거된다 -- [ ] #9 git rebase로 커밋을 재생해도 트레일러가 추가되지 않고 훅이 실패하지도 않는다 -- [ ] #10 같은 커밋에서 hooks/post-commit을 삭제한다 — 남겨두면 prepare가 넣은 트레일러에 post-commit이 Signed-off-by와 Verify-Bypassed를 또 붙여 이 저장소의 실제 이력에 잘못된 footer가 남는다 +- [ ] #7 재귀 가드가 필요 없어져 제거된다 +- [ ] #8 git rebase로 커밋을 재생해도 트레일러가 추가되지 않고 훅이 실패하지도 않는다 +- [ ] #9 같은 커밋에서 hooks/post-commit을 삭제한다 — 남겨두면 prepare가 넣은 트레일러에 post-commit이 Signed-off-by와 Verify-Bypassed를 또 붙여 이 저장소의 실제 이력에 잘못된 footer가 남는다 +- [ ] #10 Verify-Bypassed 트레일러를 더 이상 삽입하지 않는다. Signed-off-by는 계속 삽입한다(decision-25가 decision-19의 제거 조항을 대체) ## Definition of Done @@ -55,3 +56,13 @@ type: feature - [ ] #2 ruff check 통과 - [ ] #3 이 저장소 자신의 커밋이 새 훅으로 정상 생성되는지 확인 + +## Comments + + +author: @claude +created: 2026-10-03 03:14 +--- +2026-10-03: decision-25가 decision-19의 Signed-off-by 제거 조항을 대체했다(더 최근 문서인 decision-24·DRAFT-19가 Signed-off-by를 전제로 한다). 예전 AC#7(Signed-off-by와 Verify-Bypassed 미삽입)을 지우고 Signed-off-by는 유지하는 AC로 다시 적었다. 설명 본문의 'Signed-off-by를 없앤다'는 이 코멘트로 대체된다. +--- + diff --git "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" index c2252c5..7fb0f55 100644 --- "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" @@ -5,7 +5,7 @@ status: In Progress assignee: - '@claude' created_date: '2026-10-03 02:57' -updated_date: '2026-10-03 02:58' +updated_date: '2026-10-03 03:13' labels: - docs - naming @@ -46,6 +46,8 @@ README와 도구 이름은 이 프로젝트를 '커밋 메시지 형식을 정 - [ ] #6 backlog/config.yml의 project_name이 git-logbook으로 바뀐다 - [ ] #7 GF-133에 gitformat.* 설정 키와 마커 파일명을 새 이름으로 옮기는 AC가 추가된다 - [ ] #8 PR을 머지 커밋으로 병합한 뒤 GitHub 저장소를 coralstay/git-logbook으로 rename하고 로컬 origin URL을 갱신한다 +- [ ] #9 backlog 문서를 decision-25에 맞춘다 — doc-6·doc-7의 'push·서버측 범위 밖' 서술, doc-8 decision 지도(decision-23~25 추가와 decision-11·12·19 상태), doc-1·doc-5·doc-20의 예전 이름·URL +- [ ] #10 decision-19의 Signed-off-by 제거 조항이 더 최근 문서(decision-24, DRAFT-19)에 의해 대체됐음을 decision-25에 기록하고, 이를 구현할 GF-128의 AC와 README를 맞춘다 ## Definition of Done From 6a038887fb31c7464a578c6316a7462af5c99ad9 Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:17:15 +0900 Subject: [PATCH 19/20] =?UTF-8?q?[docs][backlog]=20=EB=AC=B8=EC=84=9C=206?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20decision-25=EC=97=90=20=EB=A7=9E=EC=B6=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 4117165 Tool-Calls: 10 Hooks-Commit: e892c22 Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...0-\352\260\200\354\235\264\353\223\234.md" | 20 +++++++------- ...5\354\206\214-\354\204\244\354\240\225.md" | 12 ++++----- ...5\354\206\214-\352\265\254\354\241\260.md" | 27 ++++++++----------- ...0\352\263\274-\355\225\234\352\263\204.md" | 27 ++++++++----------- ...14\355\201\254\354\212\244\353\243\250.md" | 10 ++++--- ...4\353\240\245-\354\247\200\353\217\204.md" | 21 ++++++++++++--- 6 files changed, 62 insertions(+), 55 deletions(-) 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-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" index b5d5e31..18aea05 100644 --- "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" @@ -3,7 +3,7 @@ id: doc-20 title: GitHub 원격 강제 설정 관리 — rulesets와 저장소 설정 type: guide created_date: '2026-09-26 12:54' -updated_date: '2026-09-26 12:54' +updated_date: '2026-10-03 03:16' --- # GitHub 원격 강제 설정 관리 @@ -66,15 +66,15 @@ decision-24(append-only)는 **로컬 훅만으로 달성할 수 없다.** `--no- ```sh # 병합 방식 -gh api repos/coralstay/git-format \ +gh api repos/coralstay/git-logbook \ --jq '{allow_merge_commit, allow_rebase_merge, allow_squash_merge}' # ruleset 목록 -gh api repos/coralstay/git-format/rulesets \ +gh api repos/coralstay/git-logbook/rulesets \ --jq '.[] | "\(.name) | \(.enforcement) | id=\(.id)"' # 특정 ruleset 상세 -gh api repos/coralstay/git-format/rulesets/23313297 \ +gh api repos/coralstay/git-logbook/rulesets/23313297 \ --jq '{name, enforcement, include: .conditions.ref_name.include, rules: [.rules[].type]}' ``` @@ -82,11 +82,11 @@ gh api repos/coralstay/git-format/rulesets/23313297 \ ```sh # 병합 방식 변경 -gh api -X PATCH repos/coralstay/git-format \ +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-format/rulesets/23313297 --input ruleset.json +gh api -X PUT repos/coralstay/git-logbook/rulesets/23313297 --input ruleset.json ``` ## 함정 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 상태는 이 목록이 우선한다. 문서끼리 충돌하면 가장 최근 +문서를 우선한다. From 1e307d5d79abda622b39996fdafc88621cefdd0d Mon Sep 17 00:00:00 2001 From: cpu-once <231006716+cpu-once@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:18:53 +0900 Subject: [PATCH 20/20] =?UTF-8?q?[docs][backlog]=20GF-138=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-Id: GF-138 AI-Tool: claude-code AI-Tool-Version: 2.1.285 Co-Authored-By: Claude AI-Model: claude-opus-5-5 Tokens-Used: 2484918 Tool-Calls: 6 Hooks-Commit: af8de8d Signed-off-by: cpu-once <231006716+cpu-once@users.noreply.github.com> --- ...34\353\252\205\355\225\234\353\213\244.md" | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" index 7fb0f55..72852c9 100644 --- "a/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" +++ "b/backlog/tasks/gf-138 - \355\224\204\353\241\234\354\240\235\355\212\270-\353\252\251\354\240\201\354\235\204-\354\236\254\354\240\225\354\235\230\355\225\230\352\263\240-git-logbook\354\234\274\353\241\234-\352\260\234\353\252\205\355\225\234\353\213\244.md" @@ -1,11 +1,11 @@ --- id: GF-138 title: 프로젝트 목적을 재정의하고 git-logbook으로 개명한다 -status: In Progress +status: Done assignee: - '@claude' created_date: '2026-10-03 02:57' -updated_date: '2026-10-03 03:13' +updated_date: '2026-10-03 03:18' labels: - docs - naming @@ -38,23 +38,23 @@ README와 도구 이름은 이 프로젝트를 '커밋 메시지 형식을 정 ## Acceptance Criteria -- [ ] #1 프로젝트 목적(형식·출처 기록·이력 불변)을 decision-25로 기록하고, decision-11/12의 범위 제외 조항을 대체한다고 명시한다 -- [ ] #2 decision-25가 decision-24, DRAFT-18/19/20, decision-22, 토큰 측정, AI-Model 게이트, Task-Id 강제, 에디터 금지를 목적 안의 것으로 명시한다 -- [ ] #3 README의 제목·소개·다루는 범위 섹션이 새 목적과 git-logbook 이름으로 바뀐다 -- [ ] #4 README의 clone URL이 실제 원격 coralstay/git-logbook을 가리킨다 -- [ ] #5 install.sh 출력·주석, hooks 주석, .gitmessage, CI 주석, tests의 표시 이름 git-format이 git-logbook으로 바뀐다 (설정 키·파일명 gitformat.*은 제외) -- [ ] #6 backlog/config.yml의 project_name이 git-logbook으로 바뀐다 -- [ ] #7 GF-133에 gitformat.* 설정 키와 마커 파일명을 새 이름으로 옮기는 AC가 추가된다 +- [x] #1 프로젝트 목적(형식·출처 기록·이력 불변)을 decision-25로 기록하고, decision-11/12의 범위 제외 조항을 대체한다고 명시한다 +- [x] #2 decision-25가 decision-24, DRAFT-18/19/20, decision-22, 토큰 측정, AI-Model 게이트, Task-Id 강제, 에디터 금지를 목적 안의 것으로 명시한다 +- [x] #3 README의 제목·소개·다루는 범위 섹션이 새 목적과 git-logbook 이름으로 바뀐다 +- [x] #4 README의 clone URL이 실제 원격 coralstay/git-logbook을 가리킨다 +- [x] #5 install.sh 출력·주석, hooks 주석, .gitmessage, CI 주석, tests의 표시 이름 git-format이 git-logbook으로 바뀐다 (설정 키·파일명 gitformat.*은 제외) +- [x] #6 backlog/config.yml의 project_name이 git-logbook으로 바뀐다 +- [x] #7 GF-133에 gitformat.* 설정 키와 마커 파일명을 새 이름으로 옮기는 AC가 추가된다 - [ ] #8 PR을 머지 커밋으로 병합한 뒤 GitHub 저장소를 coralstay/git-logbook으로 rename하고 로컬 origin URL을 갱신한다 -- [ ] #9 backlog 문서를 decision-25에 맞춘다 — doc-6·doc-7의 'push·서버측 범위 밖' 서술, doc-8 decision 지도(decision-23~25 추가와 decision-11·12·19 상태), doc-1·doc-5·doc-20의 예전 이름·URL -- [ ] #10 decision-19의 Signed-off-by 제거 조항이 더 최근 문서(decision-24, DRAFT-19)에 의해 대체됐음을 decision-25에 기록하고, 이를 구현할 GF-128의 AC와 README를 맞춘다 +- [x] #9 backlog 문서를 decision-25에 맞춘다 — doc-6·doc-7의 'push·서버측 범위 밖' 서술, doc-8 decision 지도(decision-23~25 추가와 decision-11·12·19 상태), doc-1·doc-5·doc-20의 예전 이름·URL +- [x] #10 decision-19의 Signed-off-by 제거 조항이 더 최근 문서(decision-24, DRAFT-19)에 의해 대체됐음을 decision-25에 기록하고, 이를 구현할 GF-128의 AC와 README를 맞춘다 ## Definition of Done -- [ ] #1 python3 -m unittest discover -s tests 전체 통과 -- [ ] #2 ruff check 통과 -- [ ] #3 git grep -i git-format -- ':!backlog' 결과에 의도한 예외만 남는다 +- [x] #1 python3 -m unittest discover -s tests 전체 통과 +- [x] #2 ruff check 통과 +- [x] #3 git grep -i git-format -- ':!backlog' 결과에 의도한 예외만 남는다 - [ ] #4 rename 뒤 git ls-remote origin 성공, 예전 URL 리다이렉트 확인 @@ -68,3 +68,15 @@ README와 도구 이름은 이 프로젝트를 '커밋 메시지 형식을 정 5. 서브에이전트로 diff 독립 검토 6. PR → CI → 머지 커밋 병합 → gh repo rename → origin URL 갱신 (AC#8) + +## Implementation Notes + + +검증: python3 -m unittest 93개 통과, ruff·shellcheck 통과. git grep -i git-format -- ':!backlog'는 README 개명 안내 줄과 tests/isolated_repo.py:1(예전 파일명 언급) 두 곳만 남음 — 둘 다 의도한 예외. 서브에이전트 독립 리뷰 지적 중 AC 범위 안은 반영(decision-25의 pre-rebase 서술 정정, README 원격 보호 범위 과장 정정, install.sh 문구). 유저 지시로 backlog 문서 정리(AC#9)와 Signed-off-by 유지(AC#10, '최근 문서 우선')를 범위에 추가. doc-7의 '[git-format] ts' 출력은 개명 전에 이미 사라진 기능의 실제 출력이라 그대로 둠. + + +## Final Summary + + +프로젝트 목적을 형식·출처 기록·이력 불변의 세 기둥으로 재정의(decision-25, decision-11·12의 범위 제외 조항과 decision-19의 Signed-off-by 제거 조항 대체)하고 표시 이름을 git-logbook으로 바꿨다. README·install.sh·훅 주석·테스트·CI·backlog config와 문서 6개를 맞추고, 설정 키 이전은 GF-133, Signed-off-by 유지는 GF-128 AC로 넘겼다. unittest 93개·ruff·shellcheck 통과. AC#8(GitHub 저장소 rename)과 DoD#4는 PR 머지 후에만 가능해 미체크 상태로 Done 처리 — rename 직후 검증하고 체크한다. +