diff --git a/.claude/hooks/token-usage/aptabase.json b/.claude/hooks/token-usage/aptabase.json new file mode 100644 index 0000000..d2a77a7 --- /dev/null +++ b/.claude/hooks/token-usage/aptabase.json @@ -0,0 +1,9 @@ +{ + "enabled": true, + "app_key": "A-SH-7756143445", + "aptabase_host": "https://aptabase.hmac.kr", + "user_name": "김민성(b16213)", + "git_repositories": [ + "D:/MYCLAUDE_PROJECT/workhistory" + ] +} diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..72bc4ae --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,38 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "t=$(mktemp);cat>\"$t\";e=./.claude/hooks/token-usage/claude-hook.exe;[ -x \"$e\" ] && \"$e\" session-context \"$t\";rm -f \"$t\"", + "timeout": 5 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "t=$(mktemp);cat>\"$t\";e=./.claude/hooks/token-usage/claude-hook.exe;[ -x \"$e\" ] && \"$e\" stop-record \"$t\";rm -f \"$t\"", + "timeout": 5 + } + ] + } + ], + "PostToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "t=$(mktemp);cat>\"$t\";e=./.claude/hooks/token-usage/claude-hook.exe;[ -x \"$e\" ] && \"$e\" aptabase-commit \"$t\";rm -f \"$t\"", + "timeout": 15 + } + ], + "matcher": "Bash" + } + ] + } +} diff --git a/.usage/token/.gitignore b/.usage/token/.gitignore new file mode 100644 index 0000000..bf19123 --- /dev/null +++ b/.usage/token/.gitignore @@ -0,0 +1,2 @@ +job-commit-pool.json +job-send-pool.json diff --git a/HOOKS.md b/HOOKS.md deleted file mode 100644 index 73f6edc..0000000 --- a/HOOKS.md +++ /dev/null @@ -1,346 +0,0 @@ -# Aptabase 훅 시스템 문서 - -> 작성: 2026-04-08 -> 대상: `workhistory` 저장소의 `.claude/hooks/` 기반 Aptabase 토큰 기록 파이프라인 - ---- - -## 1. 개요 - -Claude Code 세션에서 발생한 응답 토큰을 **git 커밋 단위로** 묶어 자체 호스팅 Aptabase(`https://aptabase.hmac.kr`)로 자동 전송하는 시스템. 토큰은 세션 동안 로컬에 누적되다가 git 커밋이 감지되면 `claude_commit` 이벤트로 flush된다. - -**핵심 아이디어** -- 커밋을 "작업 단위"의 자연스러운 경계로 사용 -- 외부(IDE/터미널/GUI)에서 일어난 커밋도 포착 (`.git/hooks/post-commit` 설치) -- 전송 실패 시 재시도, 이중 전송 방지(`last_sent_commit` 해시 비교) - ---- - -## 2. 구성 파일 - -``` -.claude/ -├── settings.json # 훅 등록 (Stop / PostToolUse(Bash)) -├── hooks/ -│ ├── aptabase.json # app_key, host, user_name -│ ├── aptabase-accumulate.ps1 # Stop 훅 진입점 (Windows) -│ ├── aptabase-accumulate.sh # Stop 훅 진입점 (Unix) -│ ├── aptabase-commit.ps1 # 커밋 flush (Windows) -│ ├── aptabase-commit.sh # 커밋 flush (Unix) -│ ├── install-git-hook.sh # .git/hooks/post-commit 설치 -│ └── usage.md # 상세 사용 설명 -├── skills/aptabase/SKILL.md # /aptabase 슬래시 스킬 정의 -└── state/ - ├── .gitignore - └── aptabase-accum.json # 누적 상태 (자동 생성) -``` - ---- - -## 3. 동작 흐름 - -| 시점 | 훅 이벤트 | 실행 스크립트 | 역할 | -|---|---|---|---| -| Claude 응답 완료 | `Stop` | `aptabase-accumulate.ps1` | 트랜스크립트 JSONL 증분 파싱 → `state.accum`에 토큰 누적. 네트워크 호출 없음. | -| Claude `Bash` 툴 호출 후 | `PostToolUse(Bash)` | `aptabase-commit.ps1` | HEAD 해시 변화 감지 → 변했으면 Aptabase POST → 누적 리셋 | -| 모든 git 커밋 (외부 포함) | `.git/hooks/post-commit` | `aptabase-commit.ps1` | 동일 로직. Claude 밖 커밋도 포착 | - -``` -Claude 응답 완료 - │ - ▼ -[Stop: accumulate.ps1] - ├─ 트랜스크립트 새 byte 구간만 읽어 assistant usage 누적 - └─ state.accum 저장 (네트워크 없음) - -git commit (Claude Bash 또는 외부) - │ - ├─ PostToolUse(Bash) ┐ - │ │ - └─ post-commit 훅 ▼ - [commit.ps1] - ├─ HEAD 가 last_sent_commit 과 같으면 exit - ├─ 다르면: - │ ├─ 남은 트랜스크립트 flush - │ ├─ consumed_tokens 계산 (0이면 전송 생략) - │ ├─ POST /api/v0/event (event: claude_commit) - │ ├─ 성공: 누적 리셋, last_sent_commit = HEAD - │ └─ 실패: state 유지 → 다음 호출에서 재시도 - └─ exit 0 (실패해도 git/Claude 흐름 비차단) -``` - ---- - -## 4. 설정 — `aptabase.json` - -현재값 ([.claude/hooks/aptabase.json](.claude/hooks/aptabase.json)): - -```json -{ - "enabled": true, - "app_key": "A-SH-7756143445", - "aptabase_host": "https://aptabase.hmac.kr", - "user_name": "김민성(b16213)", - "git_repositories": ["D:/MYCLAUDE_PROJECT/workhistory"] -} -``` - -| 키 | 의미 | -|---|---| -| `enabled` | `false` 면 훅이 즉시 종료 (일시 비활성) | -| `app_key` | Aptabase App Key (self-hosted 는 `A-SH-` 접두사) | -| `aptabase_host` | Aptabase 인스턴스 base URL | -| `user_name` | `props.user_name` 으로 전송할 사용자 식별자 | -| `git_repositories` | (참고용) 대상 저장소 목록 | - -> ⚠️ `app_key`가 평문으로 커밋되어 있다. 공개 저장소로 전환할 경우 반드시 gitignore 또는 `.example` 템플릿으로 분리할 것. - ---- - -## 5. 훅 등록 — `settings.json` - -[.claude/settings.json](.claude/settings.json)은 인라인 PowerShell 래퍼로 구성되어 있다: - -```json -{ - "hooks": { - "Stop": [{ - "hooks": [{ - "type": "command", - "command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"$j=[Console]::In.ReadToEnd();$c=try{($j|ConvertFrom-Json).cwd}catch{$null};if(-not $c){exit 0};$f=Join-Path $c '.claude\\hooks\\aptabase-accumulate.ps1';if(Test-Path $f){$t=[IO.Path]::GetTempFileName();[IO.File]::WriteAllText($t,$j,[Text.Encoding]::UTF8);try{&powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $f $t}finally{Remove-Item $t -EA 0}}\"" - }] - }], - "PostToolUse": [{ - "matcher": "Bash", - "hooks": [{ - "type": "command", - "command": "powershell ... (같은 패턴, commit.ps1 호출)" - }] - }] - } -} -``` - -**래퍼의 동작** -1. stdin으로 들어온 훅 JSON을 읽는다 -2. `cwd` 필드를 추출해 프로젝트의 `.ps1` 경로를 찾는다 -3. 훅 JSON 전체를 UTF-8 임시 파일에 쓴다 -4. `.ps1`을 `-File` 인자로 실행하며 임시 파일 경로를 넘긴다 -5. 스크립트는 `$args[0]` 또는 stdin 양쪽에서 JSON을 읽을 수 있도록 작성되어 있음 - ---- - -## 6. 전송 이벤트 — `claude_commit` - -**엔드포인트**: `POST {aptabase_host}/api/v0/event` -**헤더**: `App-Key: `, `Content-Type: application/json` - -### systemProps -`osName`, `osVersion`, `locale`, `appVersion`, `sdkVersion`, `isDebug` - -### props - -| 필드 | 출처 | -|---|---| -| `claude_oauth_id` | `~/.claude.json` 의 `oauthAccount.emailAddress` (없으면 `anonymous`) | -| `plan` | `subscriptionType` / `plan` / `billingType` (API 키는 `apikey`) | -| `user_name` | `aptabase.json` 의 `user_name` | -| `local_ip` | UDP connect 트릭 (패킷 미전송) | -| `public_ip` | `api.ipify.org` → `ifconfig.me` fallback (2초 타임아웃) | -| `commit_hash` | `git rev-parse HEAD` | -| `commit_message` | `git log -1 --pretty=%B` | -| `issue_number` | 커밋 메시지 regex 추출 (`#123`, `fixes #45` 등). 없으면 `null` | -| `repository` | remote URL 또는 git root 디렉터리 | -| `consumed_tokens` | 가중 합계 (아래 참조) | -| `total_tokens` | 단순 합계 | -| `input_tokens`, `output_tokens`, `cache_creation_tokens`, `cache_read_tokens` | 세부 | - -### consumed_tokens 가중치 - -``` -consumed = input * 1.0 - + cache_creation * 1.25 - + cache_read * 0.10 - + output * 5.0 -``` - -Anthropic 청구 단가 비율과 맞춘 "실질 비용 단위". - -### 이슈 번호 추출 regex - -``` -(?:fix(?:e[sd])?|close[sd]?|resolve[sd]?)?[\s]*#(\d+) -``` - -예: `fixes #45`, `closes#12`, `#123` 모두 매칭. - ---- - -## 7. 누적 상태 파일 — `aptabase-accum.json` - -경로: `/.claude/state/aptabase-accum.json` - -```json -{ - "input_tokens": 1234, - "cache_creation_tokens": 5678, - "cache_read_tokens": 9012, - "output_tokens": 345, - "last_sent_commit": "abc123def...", - "last_transcript": "C:\\Users\\...\\session-uuid.jsonl", - "last_transcript_offset": 45678 -} -``` - -| 필드 | 의미 | -|---|---| -| `*_tokens` | 현재 누적 중인 토큰 (flush 시 0으로 리셋) | -| `last_sent_commit` | 마지막 성공 전송한 HEAD 해시 (중복 전송 방지) | -| `last_transcript` | 마지막으로 읽은 트랜스크립트 JSONL 경로 | -| `last_transcript_offset` | 해당 파일에서 이미 읽은 byte 위치 (JSONL append-only 특성 활용) | - -**수동 리셋**: `rm .claude/state/aptabase-accum.json` -**전송 기준점만 초기화**: `last_sent_commit` 필드를 `""` 로 편집 - ---- - -## 8. 첫 실행 / 엣지 케이스 - -| 상황 | 동작 | -|---|---| -| 첫 설치 후 첫 커밋 | `last_sent_commit` 이 비어 있으면 현재 HEAD 를 기준점으로 기록만 하고 전송 안 함 | -| `consumed_tokens == 0` | 전송 생략, `last_sent_commit` 만 갱신 | -| 네트워크 실패 | state 유지 → 다음 Bash 호출에서 재시도 (누적 + 새 커밋 병합) | -| Claude가 Bash로 커밋 | PostToolUse(Bash) + post-commit 둘 다 발동. `last_sent_commit` 비교로 한 번만 전송 | -| 외부(IDE)에서 커밋 | post-commit 만 발동 | -| git 저장소 밖 실행 | `git rev-parse HEAD` 실패 → 조용히 종료 | -| OAuth 미로그인 (API 키) | `claude_oauth_id = "anonymous"`, `plan = "apikey"` | -| `amend` / `rebase` / `cherry-pick` | HEAD 해시가 바뀌므로 모두 포착 | -| `last_sent_commit` 이 삭제된 커밋 | 탐지 후 현재 HEAD 를 새 기준점으로 재설정 (누적 리셋) | - ---- - -## 9. 검증 절차 - -### 9.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-08T00:00:00.000Z", - "sessionId":"manual-test", - "eventName":"claude_commit", - "systemProps":{"osName":"Test","sdkVersion":"manual"}, - "props":{"commit_message":"manual test","total_tokens":1} - }' -``` - -`HTTP/1.1 200` + `{}` 이면 수신 OK. - -### 9.2 accumulate.ps1 직접 실행 - -```bash -echo '{"transcript_path":"","cwd":"D:/MYCLAUDE_PROJECT/workhistory","hook_event_name":"Stop"}' \ - | powershell -NoProfile -ExecutionPolicy Bypass -File .claude/hooks/aptabase-accumulate.ps1 -cat .claude/state/aptabase-accum.json -``` - -`last_transcript`, `last_transcript_offset`, 각 `*_tokens` 가 채워지면 스크립트는 정상. - -### 9.3 E2E - -1. Claude 와 대화 → 토큰 누적 -2. `cat .claude/state/aptabase-accum.json` — accum 이 0 이 아님을 확인 -3. `git commit --allow-empty -m "test: aptabase #999"` -4. Aptabase 대시보드에서 `claude_commit` 이벤트 확인 -5. state 가 0 으로 리셋되고 `last_sent_commit` 이 새 HEAD 로 갱신됐는지 확인 - ---- - -## 10. 트러블슈팅 - -### 증상 A — `accum` 이 계속 0 (현재 이 저장소의 증상) - -**확인 순서** -1. `.claude/hooks/aptabase.json` 의 `enabled: true` 여부 -2. 훅을 수동 실행했을 때 (9.2) 정상 누적되는지 → YES면 settings.json 래퍼 문제, NO면 스크립트/경로 문제 -3. `last_transcript` 가 비어 있으면 **Stop 훅이 실제로는 스크립트까지 도달하지 않은 것** -4. settings.json 의 인라인 PowerShell 래퍼를 단순화해서 재시도 (§11 참조) - -### 증상 B — 커밋했는데 대시보드에 안 뜸 - -1. curl 직접 테스트 (9.1)로 네트워크/인증 분리 검증 -2. `last_sent_commit` 이 이미 HEAD 와 같은지 (이미 전송됨) -3. `consumed_tokens == 0` 이라 전송이 생략됐는지 (accum 확인) -4. `.git/hooks/post-commit` 이 실제로 존재하는지: `cat .git/hooks/post-commit` -5. 커밋이 git 저장소 내부에서 이루어졌는지 (`git rev-parse HEAD` 성공) - -### 증상 C — 첫 커밋이 무시됨 - -**의도된 동작**. `last_sent_commit` 가 빈 상태에서는 현재 HEAD 를 기준점으로 기록만 하고 전송하지 않는다. 두 번째 커밋부터 전송된다. - -### 일시 비활성 - -`aptabase.json` 의 `enabled: false`. 훅 등록은 유지해도 된다. - -### 전송 기준점 초기화 (디버깅용) - -```bash -jq '.last_sent_commit = ""' .claude/state/aptabase-accum.json > /tmp/_s \ - && mv /tmp/_s .claude/state/aptabase-accum.json -``` - ---- - -## 11. 알려진 이슈 — Stop 훅 비활성 - -현재 이 저장소의 `aptabase-accum.json` 은 `last_transcript` 가 빈 문자열인 채로 남아 있다. 이는 **Stop 훅 래퍼가 한 번도 `.ps1` 까지 도달하지 못했음**을 의미한다 (스크립트 자체는 수동 실행 시 정상 동작 확인됨). - -### 추정 원인 - -`settings.json` 의 인라인 PowerShell `-Command` 가 Claude Code 훅 실행 쉘(git-bash 기반일 가능성)을 거치며 `$j`, `$c`, `$f`, `$t` 같은 달러 변수가 **bash에 의해 빈 문자열로 치환**되어 PowerShell 에 도달하기 전에 손상되는 것으로 보인다. - -### 권장 수정 - -인라인 래퍼 대신 단순 파일 호출로 대체: - -```json -{ - "hooks": { - "Stop": [{ - "hooks": [{ - "type": "command", - "command": "powershell -NoProfile -ExecutionPolicy Bypass -File .claude/hooks/aptabase-accumulate.ps1" - }] - }], - "PostToolUse": [{ - "matcher": "Bash", - "hooks": [{ - "type": "command", - "command": "powershell -NoProfile -ExecutionPolicy Bypass -File .claude/hooks/aptabase-commit.ps1" - }] - }] - } -} -``` - -`.ps1` 들은 이미 stdin 에서 훅 JSON 을 읽도록 작성되어 있으므로 래퍼에서 `cwd` 추출·임시파일 생성 과정은 불필요하다. - ---- - -## 12. 제거 - -```bash -# 1. git post-commit 훅 제거 -bash .claude/hooks/install-git-hook.sh --uninstall - -# 2. settings.json 에서 Stop / PostToolUse(Bash) 훅 엔트리 제거 - -# 3. 파일 삭제 (선택) -rm -rf .claude/hooks .claude/state/aptabase-accum.json -``` diff --git a/PROJECT_ANALYSIS.md b/PROJECT_ANALYSIS.md index 06fd03e..497c5dd 100644 --- a/PROJECT_ANALYSIS.md +++ b/PROJECT_ANALYSIS.md @@ -1,102 +1,115 @@ # workhistory 프로젝트 분석 -생성일: 2026-04-08 +생성일: 2026-04-09 +대상 브랜치: `main` ## 1. 개요 -`workhistory`는 개인 작업 이력·메모 저장소이자, **Claude Code 훅(Aptabase 토큰 기록) 테스트베드**로 운영되는 git 저장소다. 실제 "프로덕션 코드"는 없고, 저장소 자체가 `.claude/` 하위 훅/스킬 구성을 검증하기 위한 샌드박스다. +`workhistory`는 개인 작업 이력·메모 저장소이자, **Claude Code 훅(Aptabase 토큰 기록) 테스트베드**로 운영되는 git 저장소다. 프로덕션 코드는 없고, 저장소 자체가 `.claude/` 하위 훅 구성을 검증하기 위한 샌드박스다. -- 위치: [d:/MYCLAUDE_PROJECT/workhistory](.) -- 원격 저장소: (로컬 전용 추정) -- 현재 브랜치: `main` -- 최근 커밋: `e09c019 test: empty test commit` +- 위치: [.](.) +- 최근 커밋: `68d4012 docs: add project analysis and hooks documentation` +- 주요 산출물: `.claude/hooks/token-usage/` 의 Aptabase 연동 훅 -## 2. 디렉터리 구조 +## 2. 디렉터리 구조 (현재 상태) ``` workhistory/ -├── README.md # 저장소 목적 요약 -├── test-aptabase.txt # 훅 테스트용 더미 -├── claude-common-installer(3).exe # 공통 설정 설치 바이너리 (12MB) +├── README.md # 저장소 목적 요약 +├── SKILLS.md # (참고용) 과거 skills 문서 — 현재 skills 디렉터리는 없음 +├── claude-common-installer(4).exe # 공통 설정 설치 바이너리 (~12MB) ├── .claude/ -│ ├── settings.json # Stop / PostToolUse(Bash) 훅 등록 -│ ├── hooks/ -│ │ ├── aptabase.json # app_key, host, user_name -│ │ ├── aptabase-accumulate.{sh,ps1} # 응답 종료 시 토큰 누적 -│ │ ├── aptabase-commit.{sh,ps1} # 커밋 감지 → Aptabase flush -│ │ ├── install-git-hook.sh # .git/hooks/post-commit 설치 -│ │ └── usage.md # 상세 사용 문서 -│ ├── skills/aptabase/SKILL.md # 슬래시 스킬 정의 -│ └── state/ -│ ├── .gitignore -│ └── aptabase-accum.json # 누적 상태 (자동 생성) -└── memory/ # 장기 메모리 저장소 (README 기준) +│ ├── settings.json # UserPromptSubmit / Stop / PostToolUse(Bash) 훅 등록 +│ └── hooks/ +│ └── token-usage/ +│ ├── aptabase.json # app_key, host, user_name, git_repositories +│ └── claude-hook.exe # 단일 바이너리 (Go/Rust 추정, ~27KB) +└── .usage/ + └── token/ + ├── .gitignore + ├── job-commit-pool.json # commit-pool 대기 레코드 (현재 빈 배열) + └── job-send-pool.json # send-pool 대기 레코드 (현재 빈 배열) ``` -> 참고: [usage.md](.claude/hooks/usage.md)에 언급된 `aptabase_common.py`, `aptabase-*.py` 파이썬 구현은 실제 디렉터리에는 없고, `.sh` / `.ps1` 진입점만 존재한다. Windows 환경이라 PowerShell 경로가 실제 실행된다. +> 이전 커밋(`68d4012`)의 분석 문서에서 언급된 `.ps1` / `.sh` / `aptabase-accumulate.*` 스크립트 구조는 **더 이상 존재하지 않는다**. 현재는 모든 훅 로직이 **단일 바이너리 `claude-hook.exe`** 로 통합되었다. -## 3. 핵심 기능 — Aptabase 토큰 기록 훅 +## 3. 핵심 기능 — Aptabase 토큰 기록 훅 (v2: 단일 바이너리) ### 3.1 목적 -Claude Code 세션에서 발생한 응답 토큰을 **git 커밋 단위로** 묶어 자체 호스팅 Aptabase(`https://aptabase.hmac.kr`)로 전송. 커밋 메시지·이슈 번호·사용자 식별자와 함께 누적 토큰(input/output/cache)을 이벤트(`claude_commit`)로 기록한다. +Claude Code 세션 토큰 사용량을 **git 커밋 단위로** 묶어 self-hosted Aptabase(`https://aptabase.hmac.kr`)로 전송. 커밋 해시·메시지·이슈 번호·사용자 식별자와 함께 input/output/cache 토큰을 `claude_commit` 이벤트로 기록한다. -### 3.2 훅 파이프라인 +### 3.2 훅 파이프라인 ([settings.json](.claude/settings.json)) -| 트리거 | 훅 이벤트 | 스크립트 | 역할 | -|---|---|---|---| -| Claude 응답 완료 | `Stop` | `aptabase-accumulate.ps1` | 트랜스크립트 JSONL 증분 파싱 → `state.accum` 누적 (네트워크 없음) | -| Claude의 `Bash` 툴 호출 후 | `PostToolUse(Bash)` | `aptabase-commit.ps1` | HEAD 해시 변화 감지 시 Aptabase POST 후 리셋 | -| 모든 git 커밋 (외부 포함) | `.git/hooks/post-commit` | `aptabase-commit.sh` | Claude 밖 커밋도 포착 (clone 시 재설치 필요) | +| 이벤트 | 서브커맨드 | 역할 | +|---|---|---| +| `UserPromptSubmit` | `claude-hook.exe session-context` | 세션/프롬프트 컨텍스트 기록 (stdin JSON 수신) | +| `Stop` | `claude-hook.exe stop-record` | 응답 종료 시 토큰 누적 기록 | +| `PostToolUse` (matcher: `Bash`) | `claude-hook.exe aptabase-commit` | Bash 툴 호출 후 HEAD 해시 변화 감지 → Aptabase 전송 | -중복 전송은 `last_sent_commit` 해시 비교로 방지. +공통 패턴: stdin JSON 을 임시 파일로 받아 바이너리에 경로 인자로 넘김. `[ -x "$e" ]` 로 존재 확인 후 실행, 사용 뒤 임시 파일 정리. 타임아웃은 5s(경량) / 15s(네트워크 전송). -### 3.3 settings.json 등록 방식 ([settings.json](.claude/settings.json)) -PowerShell 인라인 래퍼로 stdin JSON을 받아 `cwd` 추출 → 프로젝트의 `.ps1`을 실행하는 구조. 이 방식 덕에 전역 설정 한 줄로 프로젝트별 훅이 동작한다. +### 3.3 상태 저장소 — [.usage/token/](.usage/token/) +- `job-commit-pool.json` — commit 감지 시점의 누적 레코드 풀 +- `job-send-pool.json` — Aptabase 전송 대기 풀 +- 둘 다 현재 `[]` 로 비어 있음 → 직전 flush 이후 신규 활동 없음 상태 -### 3.4 현재 설정값 ([aptabase.json](.claude/hooks/aptabase.json)) +> 과거 `.claude/state/aptabase-accum.json` 단일 파일 구조에서, **commit-pool / send-pool 이원화** 구조로 진화했다. 커밋 감지와 전송 단계를 분리해 네트워크 실패 시 재시도가 용이하다. + +### 3.4 설정값 ([aptabase.json](.claude/hooks/token-usage/aptabase.json)) - `enabled: true` -- `app_key: A-SH-7756143445` ⚠️ (self-hosted 키지만 평문 커밋 주의) +- `app_key: A-SH-7756143445` ⚠️ (self-hosted 키지만 평문 커밋됨) - `aptabase_host: https://aptabase.hmac.kr` - `user_name: 김민성(b16213)` - `git_repositories: ["D:/MYCLAUDE_PROJECT/workhistory"]` -### 3.5 현재 상태 ([aptabase-accum.json](.claude/state/aptabase-accum.json)) -- 누적 토큰 전부 0 (마지막 커밋에서 flush된 직후 상태) -- `last_sent_commit: e09c019…` — HEAD와 일치 → 정상 +## 4. 전송 이벤트 스키마 (`claude_commit`, 추정) -## 4. 전송 이벤트 스키마 (`claude_commit`) +바이너리 내부 구현은 직접 읽을 수 없으나, 이전 버전 / 문서 기반으로 다음 필드가 유지되는 것으로 보인다: -`props` 주요 필드: - **식별**: `claude_oauth_id`, `plan`, `user_name`, `local_ip`, `public_ip` - **커밋**: `commit_hash`, `commit_message`, `issue_number`, `repository`, `repository_url` - **토큰**: `total_tokens`, `input_tokens`, `cache_creation_tokens`, `cache_read_tokens`, `output_tokens` -이슈 번호 regex: `FEAT-123`, `BUG-45`, `closes #45`, `GH-8`, `#123` 등. +이슈 번호 regex: `FEAT-123`, `BUG-45`, `closes #45`, `GH-8`, `#123` 등 (히스토리의 `9357d85 FEAT-999` 커밋에서 추출 로직 검증). -## 5. 슬래시 스킬 +## 5. 변경점 — 이전 버전 대비 -[.claude/skills/aptabase/SKILL.md](.claude/skills/aptabase/SKILL.md) — "aptabase 설정/테스트/토큰 기록 확인/누적 토큰" 질의 시 자동 발동. 설치·검증·트러블슈팅 절차를 담은 사용자-facing 문서 역할. +이전 커밋(`68d4012`)의 [PROJECT_ANALYSIS.md](https://git) 대비 달라진 부분: -## 6. 관찰 및 리스크 +| 항목 | v1 (이전) | v2 (현재) | +|---|---|---| +| 훅 구현체 | `.ps1` + `.sh` 스크립트 다중 파일 | `claude-hook.exe` 단일 바이너리 | +| 설정 위치 | `.claude/hooks/aptabase.json` | `.claude/hooks/token-usage/aptabase.json` | +| 상태 파일 | `.claude/state/aptabase-accum.json` | `.usage/token/job-commit-pool.json`, `job-send-pool.json` | +| 훅 이벤트 | Stop + PostToolUse(Bash) + post-commit | UserPromptSubmit + Stop + PostToolUse(Bash) | +| skills | `.claude/skills/aptabase/` 존재 | **제거됨** (SKILLS.md 문서만 잔존) | +| git post-commit 훅 | `install-git-hook.sh` 로 수동 설치 | 현재 `.git/hooks/post-commit` 없음 — PostToolUse 만으로 커버 | +| HOOKS.md / PROJECT_ANALYSIS.md | 존재 | **working tree 에서 삭제됨** (git status: D) | +| test-aptabase.txt | 존재 | **삭제됨** | -1. **`app_key` 평문 커밋** — self-hosted 라 영향은 제한적이나, 외부 공개 저장소로 전환 시 반드시 gitignore 또는 환경변수로 분리 필요. -2. **`usage.md` ↔ 실제 파일 불일치** — 문서는 `.py` 기반 구현을 전제로 하나, 실제는 `.sh` / `.ps1` 만 존재. 문서 갱신 또는 `.py` 복구 중 하나가 필요. -3. **12MB 바이너리 커밋** — `claude-common-installer(3).exe`가 저장소에 포함됨. Git LFS 또는 별도 배포 채널 권장. -4. **`.git/hooks/post-commit` 재설치** — clone 시마다 `install-git-hook.sh` 수동 실행 필요. 자동화 스크립트가 없음. -5. **`memory/` 디렉터리** — README에 명시되어 있으나 현재는 비어있음(또는 미생성). 전역 메모리는 `C:\Users\nbright\.claude\projects\...\memory\` 쪽에 저장되는 것으로 보임. +방향성은 명확하다: **스크립트 난립 → 단일 바이너리 통합**, **단일 상태 파일 → pool 기반 파이프라인**, **사람 참조용 skills/docs → 코드 중심** 으로 수렴 중. -## 7. 커밋 히스토리 +## 6. 리스크 및 관찰 + +1. **`app_key` 평문 커밋** — self-hosted 라 영향은 제한적이나, 공개 전환 시 gitignore/환경변수 분리 필요. +2. **바이너리 불투명성** — `claude-hook.exe` 소스가 이 저장소에 없음. 동작 검증을 위한 소스/빌드 스크립트 위치를 README 에 링크할 필요. +3. **대용량 바이너리 커밋** — `claude-common-installer(4).exe` (~12MB) 가 저장소에 포함. Git LFS 또는 외부 배포 권장. +4. **문서 ↔ 실체 불일치** — [SKILLS.md](SKILLS.md) 는 `.claude/skills/aptabase/` 를 전제로 하지만 해당 디렉터리는 존재하지 않는다. 문서 갱신 또는 삭제 필요. +5. **working tree 의 삭제 상태** — `HOOKS.md`, `PROJECT_ANALYSIS.md`, `test-aptabase.txt` 가 git 에선 tracked 이지만 working tree 에선 삭제됨. 커밋으로 정리되지 않은 중간 상태. +6. **`.git/hooks/post-commit` 부재** — Claude 밖에서 발생한 커밋은 기록되지 않을 수 있음. PostToolUse(Bash) 만으로 커버 가능한지 확인 필요. + +## 7. 커밋 히스토리 (최근 5) ``` +68d4012 docs: add project analysis and hooks documentation e09c019 test: empty test commit a6e9e49 docs: flesh out README with repo purpose and structure 9357d85 test: aptabase flush dry-run (FEAT-999) 07660b9 test: empty test commit -e44877d Initial commit ``` -대부분의 커밋이 훅 파이프라인 E2E 검증용 empty/dry-run 커밋이다. `9357d85`에서 이슈 번호 추출 로직까지 실제 flush 검증을 마친 것으로 보인다. +대부분 훅 파이프라인 E2E 검증용 empty/dry-run 커밋. `9357d85` 에서 이슈 번호 추출까지 실제 flush 검증 완료. ## 8. 요약 -이 저장소는 **"Claude Code 사용량을 커밋 단위로 Aptabase에 자동 기록하는 훅 시스템"의 개인 실험장**이다. 핵심 산출물은 `.claude/hooks/`의 Aptabase 연동 훅 묶음이며, 나머지(README, test 파일, empty 커밋)는 모두 그 훅의 동작을 검증하기 위한 스캐폴딩이다. +`workhistory` 는 **"Claude Code 토큰 사용량을 git 커밋 단위로 self-hosted Aptabase 에 자동 기록하는 훅 시스템"의 개인 실험장**이다. v1 의 PowerShell/Bash 스크립트 묶음에서 **v2 의 `claude-hook.exe` 단일 바이너리 + commit/send pool 이원화** 구조로 진화했으며, 나머지 파일(README, installer, empty 커밋들)은 모두 그 훅 검증을 위한 스캐폴딩이다. diff --git a/SKILLS.md b/SKILLS.md new file mode 100644 index 0000000..129ca60 --- /dev/null +++ b/SKILLS.md @@ -0,0 +1,150 @@ +# Skills 문서 + +> 작성: 2026-04-08 +> 대상: `workhistory` 저장소의 `.claude/skills/` 하위 프로젝트 스킬 + +--- + +## 1. Skills 란? + +Claude Code 의 **Skill** 은 특정 상황에서 Claude 가 자동으로(또는 사용자가 슬래시 커맨드로) 참조하는 **작업 지식 꾸러미**다. `SKILL.md` 파일의 YAML 프론트매터에 `name` 과 `description` 을 선언하면, Claude 는 사용자의 메시지가 그 description 과 매칭될 때 해당 스킬 문서를 컨텍스트로 끌어온다. + +- **설치 위치**: `.claude/skills//SKILL.md` (프로젝트 단위) 또는 `~/.claude/skills/` (전역) +- **트리거 방식** + - 자동: `description` 키워드와 사용자 메시지 매칭 시 Claude 가 자발적으로 로드 + - 수동: `/skill-name` 슬래시 커맨드로 사용자가 직접 호출 +- **형식**: 프론트매터 + 본문 마크다운 + ```markdown + --- + name: my-skill + description: <어떤 상황에서 써야 하는지 한 줄 설명> + --- + + # 본문 (절차, 설정, 트러블슈팅 등) + ``` + +--- + +## 2. 이 저장소에 등록된 스킬 + +| 스킬 | 경로 | 트리거 키워드 | +|---|---|---| +| `aptabase` | [.claude/skills/aptabase/SKILL.md](.claude/skills/aptabase/SKILL.md) | "aptabase 설정", "aptabase 테스트", "토큰 기록 확인", "누적 토큰" | + +현재 프로젝트 스킬은 `aptabase` 하나뿐이다. + +--- + +## 3. `aptabase` 스킬 + +### 3.1 목적 + +Aptabase 커밋 기준 토큰 기록 훅(`.claude/hooks/` 하위)의 **설치·검증·디버깅 절차**를 Claude 가 필요할 때 즉시 참조할 수 있도록 묶어둔 운영 가이드다. + +사용자가 다음과 같은 요청을 하면 Claude 가 이 스킬을 자동 로드한다: + +- "aptabase 설정 해줘" +- "aptabase 연결 테스트해봐" +- "토큰 기록이 제대로 되고 있는지 확인해줘" +- "누적 토큰이 얼마나 쌓였어?" + +또는 수동으로 `/aptabase` 를 입력해도 동일하게 호출된다. + +### 3.2 스킬 문서가 담고 있는 내용 + +[SKILL.md](.claude/skills/aptabase/SKILL.md) 는 다음 섹션으로 구성되어 있다: + +1. **구조** — `.claude/hooks/`, `.claude/state/`, `.git/hooks/` 의 파일 구성 +2. **동작 방식** — Stop / PostToolUse(Bash) / post-commit 세 진입점의 역할표와 순서도 +3. **설정** — `aptabase.json` 의 각 키 의미 (`enabled`, `app_key`, `aptabase_host`, `user_name`) +4. **settings.json 등록 방법** — Stop 훅과 PostToolUse(Bash) 훅 예시 +5. **git post-commit 훅 설치** — `install-git-hook.sh` 사용법, `--force` / `--uninstall` 옵션 +6. **전송 payload** — `claude_commit` 이벤트의 `systemProps` / `props` 필드 스키마 +7. **연결 테스트 3단계** — curl 직접 POST → Stop 훅 수동 실행 → 실제 E2E +8. **누적 상태 파일 포맷** — `aptabase-accum.json` 의 필드 설명 +9. **트러블슈팅** — `accum` 이 0 으로 고정되는 경우, 커밋했는데 Aptabase 에 안 뜨는 경우, 첫 커밋이 무시되는 경우, 기준점 초기화 명령 + +### 3.3 실제 활용 시나리오 + +**시나리오 A — 신규 프로젝트에 훅 설치** + +사용자: "이 프로젝트에 aptabase 훅 붙여줘" +→ Claude 가 `aptabase` 스킬을 로드 → SKILL.md 의 "1. 설정" ~ "2-1. git post-commit 훅 설치" 절차를 따라 파일 복사, `aptabase.json` 작성, `settings.json` 수정, `install-git-hook.sh` 실행까지 자동 수행. + +**시나리오 B — 기록이 안 되는 것 같을 때** + +사용자: "토큰 기록이 안 되는데 확인해줘" +→ Claude 가 스킬을 로드 → SKILL.md 의 "연결 테스트 3단계" 를 순서대로 실행(curl 도달성 → Stop 훅 수동 → state 파일 확인) → "트러블슈팅" 체크리스트와 대조해 원인 보고. + +**시나리오 C — 누적 상태 확인** + +사용자: "지금 누적 토큰 얼마야?" +→ Claude 가 스킬을 로드 → `.claude/state/aptabase-accum.json` 을 읽고 필드별(input / cache_creation / cache_read / output / consumed) 로 요약. + +--- + +## 4. 새 스킬 추가 절차 + +이 저장소에 스킬을 하나 더 추가하려면: + +1. **디렉터리 생성** + ```bash + mkdir -p .claude/skills/ + ``` + +2. **SKILL.md 작성** — 프론트매터 필수 + ```markdown + --- + name: + description: <한 줄로 "어떤 요청이 왔을 때 써야 하는지" 명확히 서술. 이 문장이 자동 트리거의 유일한 근거다.> + --- + + # 본문 + ... + ``` + + > `description` 은 **Claude 가 스킬을 언제 부를지 판단하는 유일한 힌트**다. 모호한 문구("유용한 도구")는 트리거가 잘 안 되고, 키워드("X 설정", "Y 디버깅")를 명시할수록 잘 걸린다. + +3. **본문 작성 가이드** + - 사용자가 요청할 만한 상황을 먼저 나열 + - 그 상황에서 따라야 할 **구체적 절차** (명령, 경로, 체크리스트) + - 자주 발생하는 실패 케이스와 대응 + - 관련 파일 경로를 상대 링크로 걸어 Claude 가 바로 읽을 수 있게 + +4. **커밋** — `.claude/skills/` 는 버전 관리 대상. 팀원/다음 세션이 동일하게 쓸 수 있도록 커밋한다. + +5. **테스트** — 다음 세션에서 description 에 적힌 키워드로 질문해 자동 로드되는지 확인하거나, `/skill-name` 으로 직접 호출. + +--- + +## 5. 사용자 invocation — 슬래시 커맨드 + +시스템 프롬프트에 "user-invocable skills" 로 등록된 스킬은 사용자가 `/name` 형태로 직접 호출할 수 있다. 이 저장소의 `aptabase` 스킬도 `/aptabase` 로 호출 가능하다. + +Claude 입장에서는: +- 사용자가 `/aptabase` 를 입력 → Skill 툴을 통해 해당 스킬을 명시적으로 로드 +- 자동 매칭보다 우선순위가 높음 +- 이미 로드된 스킬을 또 호출하지 않음 + +--- + +## 6. 스킬 vs 훅 vs 메모리 — 구분 + +혼동하기 쉬운 세 가지 퍼시스턴스 메커니즘: + +| 구분 | 저장 위치 | 트리거 | 목적 | +|---|---|---|---| +| **Skill** | `.claude/skills//SKILL.md` | description 매칭 또는 `/name` | **작업 절차/도메인 지식** — Claude 가 어떤 일을 *어떻게* 해야 하는지 | +| **Hook** | `.claude/settings.json` + 스크립트 | 특정 이벤트 (Stop, PostToolUse 등) | **자동 실행** — Claude 와 무관하게 이벤트 발생 시 시스템이 스크립트 구동 | +| **Memory** | `~/.claude/projects/.../memory/` | 관련성 / 명시 요청 | **지속 기억** — 사용자·프로젝트·피드백·외부 참조 정보 | + +> `aptabase` 는 **스킬(운영 가이드)** + **훅(자동 기록 실행)** 의 조합으로 동작한다. +> 스킬은 "Claude 가 사람처럼 절차를 따라야 할 때" 읽는 문서이고, 훅은 "시스템이 이벤트마다 기계적으로 실행" 하는 스크립트다. 서로 교체 불가능하며 상호보완적이다. + +--- + +## 7. 참고 + +- 현재 이 저장소의 스킬 구성은 [.claude/skills/aptabase/SKILL.md](.claude/skills/aptabase/SKILL.md) 한 개만 있다. +- 전역 스킬(다른 프로젝트에서도 쓰는 것) 은 `~/.claude/skills/` 또는 플러그인(`ouroboros:*` 등)으로 제공된다. +- Aptabase 훅 자체의 상세 구현/설정은 [.claude/hooks/usage.md](.claude/hooks/usage.md) 를 참조. diff --git a/test-aptabase.txt b/test-aptabase.txt deleted file mode 100644 index 1025144..0000000 --- a/test-aptabase.txt +++ /dev/null @@ -1 +0,0 @@ -aptabase hook dry-run fixture