how2claude kayıt sistemi başka bir Rails projesine: 4 dosya, 5 adım, hook katmanlaması.
how2claude'da Claude Code session'larını otomatik kaydeden bir hooks seti var — iş başladığında kayda alır, her commit'te checkpoint ekler ve session bittiğinde prompt / bash / edit listesini docs/notes/<feature>/raw.md'ye çıkarır. let-claude-record-itself yazısı nasıl kurulduğunu anlatmıştı.
Problem: diğer projemde (smarts, akıllı sözleşme docs sitesi) bunların hiçbiri yoktu. Sonradan yazı yazmak istediğimde git log'u ve hafızamı karıştırıyor, iyi kısımları kaçırıyordum hep. Bu yazı kayıt sistemini oraya taşımayı anlatıyor — toplam 4 dosya, 5 dakika — ama yolda hook katmanlaması hakkında gerçek bir iç görü ortaya çıktı: Claude Code hooks'ları ile git hooks'ları tamamen farklı katmanlarda çalışıyor ve araçları karıştırdığınızda (örneğin Amp + Claude Code) neyin yakalandığı, hangi katmana kurduğunuza bağlı.
how2claude'da kayıtla ilgili her şey:
| Dosya | Katman | Rol |
|---|---|---|
bin/recording-state |
script | Python helper, .state.json yaşam döngüsünü yönetir |
bin/extract-session-notes |
script | Python helper, Claude Code transcript'ini okur → raw.md'ye yazar |
.claude/settings.local.json |
Claude Code hook | PostToolUse / Stop yukarıdaki script'leri tetikler |
.git/hooks/post-commit |
git hook | her commit recording-state commit çağırıp checkpoint basar |
.gitignore |
gürültü kontrolü | docs/notes/'u repo dışında tutar (notlar özel/geçici) |
4 parça, 4 farklı katman. Bu ayrım aşağıda tekrar tekrar karşımıza çıkacak.
İki hook sistemi aynı anda çalışıyor ama kapsamları çok farklı:
Claude Code hooks (.claude/settings.local.json'da tanımlı):
- Kapsam: yalnızca Claude Code aracı içinde tetiklenir
- Tetikleyiciler: PostToolUse / Stop / PreToolUse — Claude Code yaşam döngüsü olayları
- Erişilen bilgi: tool adı, argümanlar, transcript_path (tam session jsonl) — yalnızca Claude Code'un bildiği şeyler
Git hooks (.git/hooks/ altındaki shell script'leri):
- Kapsam: git aracının oluşturduğu tüm olaylarda, git'i kim tetiklerse tetiklesin
- Tetikleyiciler: post-commit / pre-push vb.
- Erişilen bilgi: git'in kendi bildiği şeyler (sha, yazar, branch, diff)
Gerçek sonuç: Claude Code içinde kod yazıp + commit yaparsanız iki katman da ateşlenir — session bilgisi ve commit bilgisi raw.md'ye iner. Amp'a (veya Cursor'a ya da elle yazmaya) geçip yazıp + commit yaparsanız, yalnızca git hook'u ateşlenir — raw.md'ye commit iskeleti düşer ama session'ın prompt / bash / edit detayı düşmez.
Bu bir bug değil — her aracın tasarım kısıtı. Her araç altında session düzeyinde detay istemek, her araca kendi hook katmanınızı kurmak demek. Git yedeği size "ne yapıldı"yı verir; "ne düşünüldü, neresi kırıldı"yı vermez.
Seçim kılavuzu:
- Araç bağımsız iskelet (commit bilgisi, kod değişiklikleri) → git hook'una koy
- Claude Code'a özgü et (tam prompt'lar, muhakeme) → Claude Code hook'una koy
- İkisi de → iki katmana da kur
Aynı kayıt kurulumunu smarts'a (/home/bob/Work/smarts, bir Rails projesi) kurmak.
mkdir -p /home/bob/Work/smarts/bin
cp /home/bob/Work/how2claude/bin/recording-state \
/home/bob/Work/smarts/bin/recording-state
cp /home/bob/Work/how2claude/bin/extract-session-notes \
/home/bob/Work/smarts/bin/extract-session-notes
chmod +x /home/bob/Work/smarts/bin/{recording-state,extract-session-notes}
Script'lerde değişiklik yok — nereye yazacaklarına $CLAUDE_PROJECT_DIR ortam değişkeni ile karar veriyorlar:
def project_dir():
return os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
def state_path():
return pathlib.Path(project_dir()) / "docs/notes/.state.json"
Kilit soyutlama bu: Claude Code hook ateşlerken CLAUDE_PROJECT_DIR'i otomatik set ediyor; biz git post-commit içinde manuel set ediyoruz. İki taraf da aynı env var'a saygı duyuyor, script "hangi projedeyim" diye düşünmek zorunda değil.
.claude/settings.local.json oluşturBurada bir karar: sadece hook'ları taşı, permissions listesini taşıma.
how2claude'un settings.local.json'ında 100'den fazla permissions.allow kaydı var — hepsi how2claude'a özel (curl localhost:3000, bin/rails runner, kamal app exec). Bunları smarts'a taşımanın hiçbir anlamı yok. smarts kullanıldıkça kendi permissions'ını organik olarak biriktirecek.
Hook'lar desen — projeler arası aynı. Permissions proje durumu — projeler arası farklı.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/bin/recording-state maybe-start"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/bin/recording-state maybe-stop"
}
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/bin/extract-session-notes" }
]
}
]
}
}
Üç tetikleme noktası:
- Dosya düzenlendi → kaydı başlatmayı dene (maybe-start kendi kontrolü: zaten kayıtta ise atla, tree clean ise yine atla)
- Bash komutu çalıştı → durdurmayı dene (maybe-stop sıkı: yalnızca "otomatik başlatıldı VE master'a döndü VE tree clean" üçü birden sağlanınca durdurur)
- Session sona erdi → transcript'i raw.md'ye çıkar
.git/hooks/post-commit oluştur3 satır:
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
CLAUDE_PROJECT_DIR="$ROOT" "$ROOT/bin/recording-state" commit || true
Manuel CLAUDE_PROJECT_DIR=$ROOT export'u, git dünyası ile Claude Code dünyasının aynı env var ile köprülendiği tam nokta. || true hook'un commit'i asla engellememesini garanti eder.
chmod +x /home/bob/Work/smarts/.git/hooks/post-commit
.gitignore'a docs/notes/ ekle# Session recording notes (transient, for article material)
docs/notes/
Notlar geçici + özel — raw.md'nin PR'lara commitlenmesini istemiyorsunuz; .state.json'un git status'u kirletmesini istemiyorsunuz. how2claude'un gitignore'u da aynı şekilde davranıyor.
Kurulumdan hemen sonra maybe-start'ı manuel tetikle:
$ cd /home/bob/Work/smarts && CLAUDE_PROJECT_DIR=$(pwd) ./bin/recording-state maybe-start
[recording] auto-started: contract-to-docs (branch: feat/contract-to-docs)
$ cat docs/notes/.state.json
{
"feature": "contract-to-docs",
"started_at": "2026-04-20T17:47:18-04:00",
"branch": "feat/contract-to-docs",
"auto_started": true
}
Script, smarts'ın o an feat/contract-to-docs branch'inde ve tree'nin dirty olduğunu doğru yakaladı — otomatik kayıt başlattı, branch feat/contract-to-docs'tan contract-to-docs feature adını çıkardı. Script'teki o mantık:
def branch_to_feature(branch):
if not branch or branch in ("master", "main"):
return None
if "/" in branch:
return branch.split("/", 1)[1]
return branch
feat/X → X, feature/X → X, çıplak fix-y → fix-y, master/main → None (zamanlı session-YYYYMMDD-HHMM ismine düşer).
Heuristik kasten aptalca basit — branch adı işin konusudur, size tekrar isim verdirmeye gerek yok.
Kurulumdan sonra kendime sordum: arada Amp'ta çalışıyorum — orası yakalanıyor mu? Cevap tam hook katmanlaması hikâyesinin öngördüğü gibi:
| Senaryo | Claude Code hook | git hook | Notlara ne düşer |
|---|---|---|---|
| Claude Code'da çalışma + commit | ✅ ateşlenir | ✅ ateşlenir | session detayı + commit iskeleti |
| Amp'ta çalışma + commit | ❌ no-op | ✅ ateşlenir | yalnızca commit iskeleti |
| Elle yazma + commit | ❌ no-op | ✅ ateşlenir | yalnızca commit iskeleti |
| Claude Code'da çalışma, henüz commit yok | ✅ kayıt başlatır | — | session detayı (commit girişi bir sonraki commit'i bekler) |
Çıkarım: Claude Code birincil sürücüyse yeter. Commit iskeleti her durumda iner; session detayı yalnızca Claude Code yolunda iner. "Ne yapıldı" tipi yazılar için çoğunlukla commit gövdesine dayanırsınız — session prompt / bash izleri bonus, olursa iyi ama olmasa da olur.
Amp'i yoğun kullanıyorsanız, Amp'in kendi hook mekanizması var (detaylarına girmedim); recording-state maybe-start/maybe-stop'u ateşleyen küçük bir yönlendirme script'i aynı şekilde çalışır.
Claude Code session recording'i başka bir projeye taşımak — 5 hamle:
cp ile hedef projenin bin/ dizinine. Değişiklik yok — script'ler $CLAUDE_PROJECT_DIR'e saygı duyar, tasarım gereği projeler arası taşınabilir..claude/settings.local.json oluştur, yalnızca hooks. Permissions listesini taşıma — permissions proje durumu (proje başına farklı); hook'lar desen (projeler arası aynı)..git/hooks/post-commit oluştur (3 satır), manuel export CLAUDE_PROJECT_DIR=$ROOT ve recording-state commit'i çağır. Git dünyası ile Claude Code dünyasının aynı env var ile köprülendiği tek nokta bu..gitignore'a docs/notes/ ekle. Notlar geçici + özel, repo'nun parçası değil.CLAUDE_PROJECT_DIR=$(pwd) ./bin/recording-state maybe-start, branch→feature çıkarımının doğru olduğunu doğrula. Tree clean ise script tasarım gereği atlar — bug değil.Asıl tasarım kararı "nasıl taşırım" değil — taşıma neredeyse cp. Asıl karar kayıt mantığını 4 ayrı katmana bölmek, her katmanın net bir iş yapması:
CLAUDE_PROJECT_DIR'e saygı duyarBu 4 katmanın her biri bağımsız olarak taşınabilir, değiştirilebilir veya atlanabilir (Amp desteği mi istiyorsunuz? bir Amp hook katmanı ekle. Not formatını mı değiştireceksiniz? Python'u düzenle. Git'in notları izlemesini mi istemiyorsunuz? post-commit'i sil). Claude kodu doğru yazabilir — ama "bu işlev hangi katmana ait" kararını sizin için veremez. Bu, sizin araç sınırlarınıza ilişkin bir karar ve o karar sizindir.