Repository navigation
chore(release): 0.1.0 판 올림 #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
982cd03
087c05f
1f8f4e5
8b44d04
4aa524e
866121b
5878938
399dbba
8598e2d
a3c5838
a8b4e5f
8730b53
2eb7b1e
81ce326
549e75a
dfd183d
0dea2e9
f819e64
bb6063a
fb8a6f0
39eee74
298152c
9ace174
9b6546a
70bbddd
73445b9
815f718
93672ed
cb66d3a
07bff60
17b430b
0d8cd64
4f5b843
073b1d1
22c4537
75136fd
5d47243
2523b1f
2ae4a09
0fc53ab
00eda67
9303b88
3cf11e6
7a4223b
5ab4b9a
022a174
8b7efcf
e8ada7d
b9344a5
8c49647
42f827d
9c64166
b610fae
6afcc2d
02518df
583ff1d
89f1439
b6acb35
cbf0507
2f1bbdb
ca3e5f7
6d19c7d
1bc461d
6f2f575
85c835c
6285f14
58e9f09
72d4ad0
0938d93
2f79070
92335ba
ac92543
fb87083
3bdcf50
60ed072
0688190
3c3516e
29f20ee
7b111f5
ea78000
1bc3864
0b29a9c
3c41d22
c021568
58372c3
ec5ed28
e553114
54686fd
13d94f8
565750a
1c62540
1bc5053
74c4a67
f21ec98
e628932
e219f67
50751aa
2dccbab
d4499cd
945e81b
6fec9b7
a23754f
46eeab8
1a041f7
b91e786
d33cec5
7e5cf15
3feb4c8
45044d3
b77a0e9
1e84e4c
ebe1d06
2714573
22d424e
2d25d34
a8a43f9
e8c35fa
f18796a
dd08a37
63c98ad
a216bce
800c417
4be9615
906ca65
801ab8e
12245c9
26cbc40
2bb3195
8b79418
fcf07d5
ec485a8
a7a83bc
b5e785f
a3f25e3
7a5736a
bc31d3d
e5597e9
62b1473
18eb562
8603155
65d69cc
242f153
0234375
330d5a3
6480328
f3fd50e
044c2bf
569bd55
ec7cc2a
3d296ba
00b1a13
e7e091f
ed4107c
7ac48ff
b4e45d0
1e44217
6e70237
ccc80a4
1369385
b2e43b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| --- | ||
| description: PR 을 올리기 전 로컬 리뷰. 기계 검사 + 영역별 리뷰 에이전트 | ||
| --- | ||
|
|
||
| PR 을 올리기 전에 **CodeRabbit 이 볼 것을 먼저 본다.** 원격에서 지적받고 고치는 | ||
| 왕복은 비싸고, 무엇보다 그 사이에 잘못된 코드가 브랜치에 남는다. | ||
|
|
||
| ## 1. 기계 검사 | ||
|
|
||
| ```bash | ||
| .claude/hooks/review-branch.sh ${1:-origin/develop} | ||
| ``` | ||
|
|
||
| 이 스크립트는 `check-java.sh`·`check-lua.sh` 를 **브랜치 변경 전체**에 태운다. | ||
| 두 훅은 `Write|Edit` 도구로 쓴 파일만 보므로, 힙독이나 스크립트로 만든 파일은 | ||
| 그냥 지나간다 — 실제로 그렇게 들어간 위반이 CodeRabbit 까지 간 적이 있다. | ||
|
|
||
| 위반이 나오면 **고치고 다시 돌린다.** 통과할 때까지 2번으로 넘어가지 않는다. | ||
|
|
||
| ## 2. 빌드와 품질 임계 | ||
|
|
||
| ```bash | ||
| ./gradlew build jacocoTestCoverageVerification pitest --no-daemon -q | ||
| ``` | ||
|
|
||
| 도메인 분기 100%, 뮤테이션 생존 ≤10%. 생존 뮤턴트가 나오면 **숫자만 보지 말고 | ||
| 어디가 살아남았는지** 본다 — 임계를 통과해도 이미 버그가 났던 자리에 몰려 | ||
| 있으면 그건 통과가 아니다. | ||
|
|
||
| ## 3. 영역별 리뷰 에이전트 | ||
|
|
||
| 변경 경로에 따라 `.claude/agents/` 에서 고른다. 1번 스크립트가 마지막에 | ||
| 어느 에이전트를 돌릴지 알려준다. | ||
|
|
||
| | 변경 | 에이전트 | | ||
| |---|---| | ||
| | `domain/**` | `domain-guardian` | | ||
| | `*.lua` · `redis/**` | `redis-cluster-checker` | | ||
| | 장애·회복·서킷·리트라이 | `resilience-auditor` | | ||
| | `src/test/**` · `src/testFixtures/**` | `test-quality-reviewer` | | ||
| | 전 영역 (항상) | `style-enforcer` | | ||
|
|
||
| 각 에이전트에게 **변경 범위를 명시**해서 넘긴다 — 저장소 전체를 훑게 하면 | ||
| 이번 변경과 무관한 지적이 섞여 진짜 지적이 묻힌다. | ||
|
|
||
| ## 4. 사람이 볼 것 | ||
|
|
||
| 기계도 에이전트도 못 보는 것이 남는다. | ||
|
|
||
| - **판정 순서를 바꿨다면** 계획서의 사다리 표도 함께 고쳤는가 (PK-A4) | ||
| - **계획서와 구현이 어긋난다면** 어느 쪽이 목표에 가까운가. 계획서가 스스로 | ||
| 모순인 경우가 있다 (AIJ-0012) | ||
| - **테스트 이름이 실제로 검증하는 것과 같은가.** 이름만 맞고 내용이 다른 | ||
| 테스트는 통과하면서 버그를 덮는다 | ||
|
|
||
| ## 5. 그다음 | ||
|
|
||
| - 저널이 필요한 변경인가 — `src/**`·`*.gradle`·`.github/workflows/**` | ||
| - 커밋 제목이 50칸 이내 명사형인가, 푸터에 `Refs: CY-###` 가 있는가 | ||
| - 푸시 전에 **무엇을 바꿨는지 요약**한다 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -90,10 +90,30 @@ fi | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # ── JS-14 중첩 클래스는 static ──────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||
| # 들여쓰기된 class 선언 = 중첩. 수식어가 없는 경우도 잡는다. | ||||||||||||||||||||||||||||||||||||||||||
| report "JS-14" "중첩 클래스는 static — 바깥 인스턴스를 붙들어 누수를 만든다" \ | ||||||||||||||||||||||||||||||||||||||||||
| "$(scan 'JS-14' \ | ||||||||||||||||||||||||||||||||||||||||||
| '^[0-9]+:[[:space:]]+((public|protected|private|final|abstract)[[:space:]]+)*class[[:space:]]' \ | ||||||||||||||||||||||||||||||||||||||||||
| 'static')" | ||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||
| # **JUnit 5 의 @Nested 는 제외한다.** 그쪽은 static 이면 아예 실행되지 않는다 — | ||||||||||||||||||||||||||||||||||||||||||
| # 규칙과 프레임워크가 충돌하는 자리라 규칙이 진다. 여기서 오탐을 내면 사람은 | ||||||||||||||||||||||||||||||||||||||||||
| # 훅을 고치는 대신 우회하고, 그러면 진짜 위반도 같이 지나간다. | ||||||||||||||||||||||||||||||||||||||||||
| # @Nested 이후 **선언까지의 연속 어노테이션 줄**을 전부 건너뛴다. 개수를 못 | ||||||||||||||||||||||||||||||||||||||||||
| # 박으면 @Tag 하나 붙는 순간 오탐이 되고, 오탐이 나면 훅이 우회된다. | ||||||||||||||||||||||||||||||||||||||||||
| nested_class_lines=$(awk ' | ||||||||||||||||||||||||||||||||||||||||||
| # `@Nested class Inner {` 처럼 한 줄에 같이 오면 그 줄이 곧 선언이다. | ||||||||||||||||||||||||||||||||||||||||||
| # pending 을 켠 채 넘어가면 **다음 중첩 클래스가 대신 면제된다.** | ||||||||||||||||||||||||||||||||||||||||||
| /^[[:space:]]*@Nested([[:space:]]|\(|$)/ && /class[[:space:]]/ { print NR; pending=0; next } | ||||||||||||||||||||||||||||||||||||||||||
| /^[[:space:]]*@Nested([[:space:]]|\(|$)/ { pending=1; next } | ||||||||||||||||||||||||||||||||||||||||||
| # 어노테이션 인자가 여러 줄에 걸치면 이어지는 줄은 @ 로 시작하지 않는다. | ||||||||||||||||||||||||||||||||||||||||||
| # 개수나 형태를 못 박지 말고 **선언 줄을 만날 때까지** 건너뛴다. | ||||||||||||||||||||||||||||||||||||||||||
| pending && /class[[:space:]]/ { print NR; pending=0; next } | ||||||||||||||||||||||||||||||||||||||||||
| pending { next } | ||||||||||||||||||||||||||||||||||||||||||
| ' "$file") | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 어노테이션 인자에 Line 106 은 ♻️ 제안 수정- pending && /class[[:space:]]/ { print NR; pending=0; next }
+ pending && /^[[:space:]]*((public|protected|private|final|abstract|static)[[:space:]]+)*class[[:space:]]+[A-Za-z_$]/ { print NR; pending=0; next }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| js14=$(scan 'JS-14' \ | ||||||||||||||||||||||||||||||||||||||||||
| '^[0-9]+:[[:space:]]+((public|protected|private|final|abstract)[[:space:]]+)*class[[:space:]]' \ | ||||||||||||||||||||||||||||||||||||||||||
| 'static') | ||||||||||||||||||||||||||||||||||||||||||
| for n in $nested_class_lines; do | ||||||||||||||||||||||||||||||||||||||||||
| js14=$(printf '%s\n' "$js14" | grep -vE "^[[:space:]]*$n:") | ||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||
| js14=$(printf '%s' "$js14" | grep -v '^[[:space:]]*$') | ||||||||||||||||||||||||||||||||||||||||||
| report "JS-14" "중첩 클래스는 static — 바깥 인스턴스를 붙들어 누수를 만든다" "$js14" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # ── JS-6 Javadoc 5줄 초과 (원본에서 검사한다) ───────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||
| hits=$(awk ' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,76 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||
| # PR 을 올리기 전에 로컬 리뷰를 강제한다. | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # **왜 차단인가.** "올리기 전에 돌려라" 는 규범은 잊힌다. 실제로 잊었고, | ||||||||||||||||||||||||||||||||||||||||||||||
| # CodeRabbit 이 두 라운드에 걸쳐 14건을 지적했는데 그중 셋은 우리 자신의 | ||||||||||||||||||||||||||||||||||||||||||||||
| # MUST 규칙 위반이었다 — 로컬에서 1초면 잡히는 것들이다. | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # PreToolUse(Bash) 훅. `gh pr create` 를 만나면 기계 검사를 돌리고 | ||||||||||||||||||||||||||||||||||||||||||||||
| # 위반이 있으면 exit 2 로 막는다. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| set -uo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| input=$(cat) | ||||||||||||||||||||||||||||||||||||||||||||||
| cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # PR 생성이 아니면 통과 | ||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$cmd" != *"gh pr create"* ]] && exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # **검사를 못 돌리면 막는다.** 통과시키면 게이트가 인프라 오류 한 번에 | ||||||||||||||||||||||||||||||||||||||||||||||
| # 조용히 사라진다 — 가드는 fail closed 여야 한다. | ||||||||||||||||||||||||||||||||||||||||||||||
| if ! ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "git 저장소가 아니라 로컬 리뷰를 돌릴 수 없다. PR 은 저장소 안에서 연다." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
| RUNNER="$ROOT/.claude/hooks/review-branch.sh" | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ ! -x "$RUNNER" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "로컬 리뷰 러너를 실행할 수 없다: $RUNNER" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||
| echo " chmod +x .claude/hooks/*.sh" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # base 를 명령에서 뽑는다. 없으면 develop | ||||||||||||||||||||||||||||||||||||||||||||||
| # **명령 문자열 전체를 훑지 않는다.** `--title "--base release"` 처럼 인용부호 | ||||||||||||||||||||||||||||||||||||||||||||||
| # 안에 들어간 값을 옵션으로 착각한다. 인자를 토큰으로 쪼갠 뒤 옵션 자리만 본다. | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # 실행하지 않고 쪼갠다 — `xargs` 는 셸 인용 규칙을 그대로 따르면서 명령을 | ||||||||||||||||||||||||||||||||||||||||||||||
| # 부르지 않는다. | ||||||||||||||||||||||||||||||||||||||||||||||
| base="" | ||||||||||||||||||||||||||||||||||||||||||||||
| mapfile -t args < <(printf '%s' "$cmd" | xargs -n1 printf '%s\n' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Line 39 는 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| for ((i = 0; i < ${#args[@]}; i++)); do | ||||||||||||||||||||||||||||||||||||||||||||||
| case "${args[i]}" in | ||||||||||||||||||||||||||||||||||||||||||||||
| --base=*) base="${args[i]#--base=}"; break ;; | ||||||||||||||||||||||||||||||||||||||||||||||
| -B=*) base="${args[i]#-B=}"; break ;; | ||||||||||||||||||||||||||||||||||||||||||||||
| --base|-B) | ||||||||||||||||||||||||||||||||||||||||||||||
| base="${args[i + 1]:-}" | ||||||||||||||||||||||||||||||||||||||||||||||
| break ;; | ||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||
| base="${base:-develop}" | ||||||||||||||||||||||||||||||||||||||||||||||
| # 이미 접두가 붙어 있으면 겹치지 않게 둔다. origin/origin/develop 이 되면 | ||||||||||||||||||||||||||||||||||||||||||||||
| # 러너가 폴백을 타고, 폴백마저 없으면 브랜치 커밋을 하나도 안 보고 통과한다. | ||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$base" != origin/* ]] && base="origin/$base" | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Line 45 는 다음 토큰이 없을 때 빈 값을 넣는다. 그러면 Line 49 가 🛡️ 제안 수정 --base|-B)
base="${args[i + 1]:-}"
+ if [[ -z "$base" ]]; then
+ echo "--base 값이 비었다. 기준을 명시한다." >&2
+ exit 2
+ fi
break ;;이 지적은 학습 내용("preserve the explicitly requested PR base in both 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Learnings |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| out=$("$RUNNER" "$base" 2>&1) | ||||||||||||||||||||||||||||||||||||||||||||||
| status=$? | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if ((status != 0)); then | ||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "PR 을 올리기 전에 로컬 리뷰가 통과해야 한다." | ||||||||||||||||||||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||||||||||||||||||||
| # 전체를 보여 준다. 걸러내면 정작 필요한 줄이 빠진다. | ||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s\n' "$out" | ||||||||||||||||||||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "고친 뒤 다시 시도한다. 수동 실행: .claude/hooks/review-branch.sh $base" | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "전체 절차: /review" | ||||||||||||||||||||||||||||||||||||||||||||||
| } >&2 | ||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # 통과했어도 기계가 못 보는 것이 남는다 — 막지는 않고 알린다. | ||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||
| echo "로컬 기계 검사 통과. 아직 안 한 것이 있는지 본다:" | ||||||||||||||||||||||||||||||||||||||||||||||
| echo " · ./gradlew build jacocoTestCoverageVerification pitest" | ||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s\n' "$out" | sed -n '/사람·에이전트가 볼 것/,$p' | sed 's/^/ /' | ||||||||||||||||||||||||||||||||||||||||||||||
| } >&2 | ||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
공용 규칙 파일을
source하기 전에 존재를 확인하지 않는다. 두 훅 모두.githooks/lib/commit-subject-rules.sh를 무조건source한다. 파일이 없으면check_commit_subject가 정의되지 않고, 명령 치환이 127 로 끝나 위반 목록이 빈 상태로 차단된다. 차단 방향은 안전하지만 원인이 출력에 남지 않는다..claude/hooks/check-commit-msg.sh#L40-L42: 경로를 변수에 담고[[ -r ... ]]로 확인한 뒤 실패 이유를 stderr 로 출력하고 exit 2 한다..githooks/commit-msg#L12-L13: 같은 확인을 추가하고 실패 시 exit 1 로 끝낸다.📍 Affects 2 files
.claude/hooks/check-commit-msg.sh#L40-L42(this comment).githooks/commit-msg#L12-L13🤖 Prompt for AI Agents