Files
recordingtest/.claude/skills/aptabase/SKILL.md
2026-04-07 20:35:45 +09:00

230 lines
8.4 KiB
Markdown

---
name: aptabase
description: Aptabase 커밋 기준 토큰 기록 훅을 설치/검증/디버깅한다. 사용자가 "aptabase 설정", "aptabase 테스트", "토큰 기록 확인", "누적 토큰"을 요청할 때 사용.
---
# Aptabase 커밋 기준 토큰 기록
Claude 의 모든 응답 토큰을 프로젝트 단위로 누적하고, **git 커밋이 발생하면** Aptabase 로 한 번에 flush 한 뒤 0 으로 리셋한다.
## 구조
```
.claude/hooks/
├── aptabase.json # 설정 (app_key, aptabase_host, user_name)
├── aptabase_common.py # 공통 유틸 (Python)
├── aptabase-accumulate.sh # Stop 훅 진입점
├── aptabase-accumulate.py # Stop 로직 — 토큰 누적만
├── aptabase-commit.sh # 커밋 flush 진입점 (Claude 훅 + git 훅 공용)
├── aptabase-commit.py # 커밋 감지 + Aptabase POST + 리셋
└── install-git-hook.sh # .git/hooks/post-commit 설치 스크립트
.claude/state/
└── aptabase-accum.json # 누적 상태 (자동 생성, git root 기준)
.git/hooks/
└── post-commit # install-git-hook.sh 가 설치 (모든 커밋 포착)
```
## 동작 방식
| 시점 | 훅 | 하는 일 |
|---|---|---|
| 응답 완료 | `Stop``aptabase-accumulate.sh` | 트랜스크립트 새 영역 파싱 → `state.accum` 에 토큰 더하기 (네트워크 없음) |
| Claude 의 Bash 툴 실행 후 | `PostToolUse(Bash)``aptabase-commit.sh` | HEAD 변화 감지. 바뀌었으면 flush → POST → 리셋 |
| **모든 커밋** (IDE / 터미널 / GUI) | `.git/hooks/post-commit``aptabase-commit.sh` | 같은 로직. Claude 밖에서 이루어진 커밋도 포착 |
`aptabase-commit.py` 는 stdin 에 JSON 이 있으면 Claude 훅 모드, 비어 있으면 git 훅 모드로 동작한다. 두 경로가 중복 실행돼도 `last_sent_commit` 비교 덕에 이중 전송되지 않는다.
- **누적 범위**: `<git-root>/.claude/state/aptabase-accum.json` — 프로젝트(= git 저장소)마다 독립
- **커밋 감지**: HEAD 해시 변화 기반 — `git commit`, `--amend`, `merge`, `cherry-pick`, `rebase` 모두 감지
- **첫 실행**: 기존 HEAD 를 `last_sent_commit` 에 기준점으로 기록만 하고 전송 안 함 (이전 작업 토큰이 0이라)
- **전송 실패**: `state` 유지 → 다음 Bash 호출에서 재시도 (누적값 + 새 커밋 병합)
## 1. 설정
`.claude/hooks/aptabase.json`:
```json
{
"enabled": true,
"app_key": "A-SH-XXXXXXXXXX",
"aptabase_host": "https://aptabase.example.com",
"user_name": "kim"
}
```
| 키 | 의미 |
|---|---|
| `enabled` | false 로 두면 훅이 즉시 종료 (일시 비활성) |
| `app_key` | Aptabase App Key (self-hosted 는 `A-SH-` 접두사) |
| `aptabase_host` | Aptabase 인스턴스 base URL |
| `user_name` | props.user_name 으로 전송할 사용자 식별자 |
## 2. settings.json 에 훅 등록
`.claude/settings.json``hooks` 에 두 항목 모두 등록:
```json
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/aptabase-accumulate.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/aptabase-commit.sh" }
]
}
]
}
}
```
이미 다른 Stop / PostToolUse 훅이 있으면 같은 `hooks` 배열에 추가한다.
## 2-1. git post-commit 훅 설치 (필수)
Claude 외부에서 이루어진 커밋(IDE, 터미널, git GUI 등)도 포착하려면 `.git/hooks/post-commit` 을 설치해야 한다. 프로젝트 루트에서 한 번만 실행:
```bash
bash .claude/hooks/install-git-hook.sh
```
이미 `.git/hooks/post-commit` 이 존재하면 에러가 나면서 병합 방법을 안내한다. 기존 훅을 덮어쓰려면:
```bash
bash .claude/hooks/install-git-hook.sh --force # 기존 훅을 .bak 로 백업하고 덮어쓰기
```
제거:
```bash
bash .claude/hooks/install-git-hook.sh --uninstall
```
> `.git/hooks/` 는 버전 관리되지 않으므로 **clone 할 때마다 재설치 필요**하다.
> CI/자동 설정 스크립트에서 프로젝트 초기화 시 함께 실행하는 것을 권장한다.
## 3. 전송되는 payload
Aptabase 이벤트 이름은 **`claude_commit`** (고정).
### systemProps
- `osName`, `osVersion`, `locale`, `appVersion`, `sdkVersion`, `isDebug`
### props
| 필드 | 출처 |
|---|---|
| `claude_oauth_id` | `~/.claude/config.json``oauthAccount.emailAddress` (없으면 `anonymous`) |
| `plan` | `oauthAccount.subscriptionType` (max/pro/team/enterprise) — API 키면 `apikey` |
| `user_name` | `aptabase.json``user_name` |
| `local_ip` | UDP connect 트릭으로 추출 (패킷 미전송) |
| `public_ip` | `api.ipify.org` / `ifconfig.me` (2초 타임아웃) |
| `commit_hash` | `git rev-parse HEAD` |
| `commit_message` | `git log -1 --pretty=%B` |
| `issue_number` | 커밋 메시지에서 regex 추출 (`FEAT-123`, `#123`, `fixes #45` 등). 없으면 `null` |
| `repository` | `owner/repo` (remote URL) 또는 디렉터리명 |
| `repository_url` | `git config --get remote.origin.url` |
| `total_tokens` | 누적 합계 |
| `input_tokens`, `cache_creation_tokens`, `cache_read_tokens`, `output_tokens` | 누적 세부 |
> Aptabase 최상위 `sessionId` 는 epoch+random 으로 매 이벤트 새로 생성된다.
> Claude 세션 UUID 는 사용하지 않는다.
## 4. 연결 테스트
**단계 1 — Aptabase 도달 가능성 확인 (curl):**
```bash
APP_KEY=$(jq -r .app_key .claude/hooks/aptabase.json)
HOST=$(jq -r .aptabase_host .claude/hooks/aptabase.json)
curl -i -X POST "$HOST/api/v0/event" \
-H "Content-Type: application/json" \
-H "App-Key: $APP_KEY" \
-H "User-Agent: ClaudeCodeHook/test" \
-d '{
"timestamp":"2026-04-07T00:00:00.000Z",
"sessionId":"manual-test",
"eventName":"claude_commit",
"systemProps":{"osName":"Test","sdkVersion":"manual"},
"props":{"commit_message":"manual test","total_tokens":1}
}'
```
200 + `{}` 이면 Aptabase 수신 OK.
**단계 2 — 훅 직접 실행 (Stop 누적):**
```bash
echo '{"transcript_path":"","cwd":"'"$PWD"'","hook_event_name":"Stop"}' \
| bash .claude/hooks/aptabase-accumulate.sh
cat .claude/state/aptabase-accum.json
```
transcript_path 가 비어있으면 accum 은 0 그대로지만, state 파일은 생성돼야 한다.
**단계 3 — 실제 동작 확인:**
1. Claude 와 대화해서 토큰 쌓기
2. `cat .claude/state/aptabase-accum.json` 으로 누적 확인
3. `git commit` 실행
4. Aptabase 대시보드에서 `claude_commit` 이벤트 확인
5. `cat .claude/state/aptabase-accum.json``accum` 이 0 으로 리셋, `last_sent_commit` 갱신됐는지 확인
## 5. 누적 상태 파일 포맷
`<git-root>/.claude/state/aptabase-accum.json`:
```json
{
"accum": {
"input_tokens": 1234,
"cache_creation_tokens": 5678,
"cache_read_tokens": 9012,
"output_tokens": 345
},
"offsets": {
"C:\\Users\\...\\projects\\d--foo\\session-uuid.jsonl": 45678
},
"last_sent_commit": "abc123def..."
}
```
- `offsets`: 트랜스크립트 JSONL 파일별로 이미 읽은 byte 위치. append-only 특성 덕에 중복 집계 방지
- 수동 리셋이 필요하면 `rm .claude/state/aptabase-accum.json`
## 6. 트러블슈팅
**누적이 안 쌓임 (`accum` 이 계속 0):**
1. `aptabase.json``enabled: true`
2. `app_key`, `aptabase_host` 실제 값
3. settings.json 의 Stop 훅에 `aptabase-accumulate.sh` 등록
4. `transcript_path` 가 훅 입력 JSON 에 실제로 있는지
5. 해당 파일 읽기 권한
**커밋했는데 Aptabase 에 안 뜸:**
1. settings.json 의 PostToolUse(Bash) 훅에 `aptabase-commit.sh` 등록
2. `last_sent_commit` 가 이미 현재 HEAD 와 같은지 (이미 전송된 상태)
3. curl 로 직접 POST 했을 때 200 이 오는지 (네트워크/인증 분리 검증)
4. `python3 --version` 동작
5. git 저장소 안에서 동작 중인지 (`git rev-parse HEAD` 성공해야 함)
**첫 커밋이 무시됨:**
의도된 동작. `last_sent_commit` 가 빈 상태일 때는 현재 HEAD 를 기준점으로 기록만 하고 전송하지 않는다. 다음 커밋부터 전송된다.
**전송 기준점 초기화:**
```bash
jq '.last_sent_commit = ""' .claude/state/aptabase-accum.json > /tmp/_s && mv /tmp/_s .claude/state/aptabase-accum.json
```
**일시 비활성:**
`aptabase.json` 에서 `enabled: false`. 훅 등록은 유지해도 된다.