commit 과 push test

This commit is contained in:
minsung
2026-04-07 20:35:45 +09:00
parent de0ca9876a
commit b20ec32c36
12 changed files with 1826 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""
커밋 flush 훅: git HEAD 가 변했으면 누적 토큰을 Aptabase 로 전송한다.
호출 경로 두 가지:
1. Claude PostToolUse(Bash) 훅 → stdin 에 JSON 입력
2. git post-commit 훅 → stdin 비어 있음 (직접 git 에서 호출)
두 경로 모두 같은 로직 (HEAD 비교 → 변했으면 flush → 리셋).
전송 시점:
- 훅 호출 시마다 HEAD 확인
- state.last_sent_commit 와 다르면 새 커밋으로 간주 → flush
전송 후 동작:
- state.accum 을 0으로 리셋
- state.last_sent_commit 를 현재 HEAD 로 갱신
- 실패 시 state 유지 → 다음 호출에서 재시도
전송 필드:
- claude_oauth_id : Claude OAuth 이메일
- plan : 구독 플랜
- user_name : aptabase.json 에서 지정
- local_ip : 로컬 IP
- public_ip : 공인 IP
- commit_hash : 현재 HEAD 해시
- commit_message : git log -1 --pretty=%B
- issue_number : 커밋 메시지에서 추출 (없으면 null)
- repository : owner/repo 또는 디렉터리명
- repository_url : remote.origin.url
- total_tokens : 누적 합계
- input_tokens, cache_creation_tokens, cache_read_tokens, output_tokens
"""
import json
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aptabase_common import ( # noqa: E402
EMPTY_TOTALS,
EVENT_NAME,
accumulate_from_transcript,
build_system_props,
consumed_tokens,
extract_issue_number,
get_claude_oauth_id,
get_local_ip,
get_plan,
get_public_ip,
get_repository_info,
load_config,
load_state,
make_aptabase_session_id,
now_iso,
post_event,
run_git,
save_state,
total_tokens,
)
def main() -> None:
cfg = load_config()
if cfg is None:
return
# stdin 파싱: Claude 훅 모드(JSON) vs git post-commit 모드(비어 있음)
hook_input: dict = {}
try:
raw = sys.stdin.read()
if raw.strip():
hook_input = json.loads(raw)
except Exception:
hook_input = {}
# Claude 훅 모드면 Bash 툴에서 온 호출만 처리 (다른 툴은 무시)
# git 훅 모드면 hook_input 이 비어 있어서 이 체크를 통과한다
if hook_input and hook_input.get("tool_name") != "Bash":
return
cwd = hook_input.get("cwd") or os.getcwd()
# git 저장소가 아니면 무시
head = run_git(["rev-parse", "HEAD"], cwd=cwd)
if not head:
return
state = load_state(cwd)
last_sent = state.get("last_sent_commit", "")
if head == last_sent:
return
if not last_sent:
# 첫 실행 — 기준점만 기록하고 다음 커밋을 기다린다
state["last_sent_commit"] = head
save_state(state, cwd)
return
# HEAD 가 바뀌었다 — Stop 훅이 놓쳤을 수 있는 최신 토큰을 flush.
# transcript_path 우선순위:
# 1) hook_input (Claude PostToolUse 훅 모드)
# 2) state.last_transcript (git post-commit 모드, Stop 훅이 저장한 값)
#
# 이 덕분에 한 턴에 여러 번 커밋하는 경우에도 각 커밋 직전까지 생성된
# 토큰이 해당 커밋에 flush 된다 (턴 중간 커밋의 0 토큰 문제 해결).
transcript_path = (
hook_input.get("transcript_path")
or state.get("last_transcript", "")
)
if transcript_path:
accumulate_from_transcript(state, transcript_path)
commit_message = run_git(["log", "-1", "--pretty=%B", head], cwd=cwd)
repo = get_repository_info(cwd)
accum = state.get("accum", dict(EMPTY_TOTALS))
payload = {
"timestamp": now_iso(),
"sessionId": make_aptabase_session_id(),
"eventName": EVENT_NAME,
"systemProps": build_system_props(),
"props": {
# 커밋 정보
"commit_hash": head,
"commit_message": commit_message,
"issue_number": extract_issue_number(commit_message),
"repository": repo["name"] if not repo["url"] else repo["url"],
# 사용자
"claude_oauth_id": get_claude_oauth_id(),
"plan": get_plan(),
"user_name": cfg.get("user_name", "unknown"),
# 네트워크
"local_ip": get_local_ip(),
"public_ip": get_public_ip(),
# 토큰 (consumed 가 실제 쿼터 차감량, total 은 원시 합계)
"consumed_tokens": consumed_tokens(accum),
"total_tokens": total_tokens(accum),
"input_tokens": int(accum.get("input_tokens", 0)),
"output_tokens": int(accum.get("output_tokens", 0)),
"cache_creation_tokens": int(accum.get("cache_creation_tokens", 0)),
"cache_read_tokens": int(accum.get("cache_read_tokens", 0)),
},
}
ok = post_event(cfg["aptabase_host"], cfg["app_key"], payload)
if ok:
# 누적 리셋 + 전송 기준점 갱신
state["accum"] = dict(EMPTY_TOTALS)
state["last_sent_commit"] = head
save_state(state, cwd)
# 실패: state 유지 → 다음 Bash 호출에서 재시도 (누적 + 새 커밋 병합)
if __name__ == "__main__":
main()