commit 과 push test
This commit is contained in:
157
.claude/hooks/aptabase-commit.py
Normal file
157
.claude/hooks/aptabase-commit.py
Normal 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()
|
||||
Reference in New Issue
Block a user