Gerçek vaka yazılarının en zor kısmı malzeme toplamak: git log'da sadece commit message'lar var, session ayrıntıları yok oluyor. 4 hook + 2 slash command her oturumu raw.md'ye çeviriyor — marjinal maliyet sıfır.
"Claude benim için X yaptı" tarzı gerçek vaka yazısı yazmanın en zor kısmı yazmak değil — malzeme toplamak. git log'da sadece commit mesajları var; reddettiğin yaklaşımlar, başarısız denemeler, üç kez revize ettiğin prompt, hata çıktısı — yazının okunmaya değer kısmı budur, ve hepsi gitti.
Daha kötüsü: yazıyı yazdığın zaman A'yı B'ye neden tercih ettiğini hatırlamıyorsun bile. "Sanırım bir sebebim vardı" ile "o sebep X'ti" arasında bir yazının inandırıcılık farkı var.
Toplantı notu tutmak yazıyı yazmaktan pahalı, o yüzden tutmuyorsun. Tek çözüm kaydın kendiliğinden olması — hook'lar.
Claude'a her dev oturumunu docs/notes/<feature>/raw.md dosyasına dönüştüren 4 hook + 2 slash command yazdırdım. Bir önceki yazı Claude'a Prodüksiyon Deploy Yaptırmak'ta alıntılanan commit'lerin, bash parçacıklarının ve hata referanslarının çoğu bu pipeline'dan geldi. Bu yazı da öyle.
4 hook + 2 manuel slash command tek bir bant oluşturuyor:
| Tetikleyici | Ne yapıyor |
|---|---|
| PostToolUse(Edit\ | Write\ |
| PostToolUse(Bash) | master'a dönünce + temizse kaydı durdur |
| git post-commit | Her commit için bir checkpoint yaz |
| Stop | Session bitince transcript'i ayrıştır |
/record-feature NAME /stop-recording |
Manuel geçersiz kılma |
Tek state dosyası: docs/notes/.state.json. Tüm hook'lar onu okuyup yazıyor. Başka koordinasyon yok.
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/bin/recording-state maybe-start"
}]
}
]
maybe-start çekirdeği:
def cmd_maybe_start():
if load_state(): return # zaten kayıtta
if not tree_is_dirty(): return # değişen yok, atla
feature = branch_to_feature(current_branch())
if not feature:
feature = "session-" + datetime.now().strftime("%Y%m%d-%H%M")
save_state({"feature": feature, "auto_started": True, ...})
branch_to_feature("feature/pro") "pro" döndürür. Developer'lar işi zaten branch'e böler, o yüzden o zihinsel sınırı yeniden kullanıyoruz — bu pipeline'ın hayatta kalmasının kök nedeni kullanıcıdan hiç yeni bir şey hatırlamasını istememesi. master'da tek seferlik script'ler timestamp'e fallback ediyor.
Matcher neden Edit/Write/MultiEdit: kod yazmak oturumda "gerçekten bir şey yapıyorum" gerçek sinyalidir. Dosya okumak, test çalıştırmak, soru sormak — sayılmıyor.
{
"matcher": "Bash",
"hooks": [{ "command": "$CLAUDE_PROJECT_DIR/bin/recording-state maybe-stop" }]
}
def cmd_maybe_stop():
state = load_state()
if not state or not state.get("auto_started"): return
if current_branch() not in ("master", "main"): return
if tree_is_dirty(): return
clear_state()
Üç kapı: manuel kayıtlar durmaz, feature branch'lerde durmaz, ağaç kirliyken durmaz.
Neden Edit yerine Bash'e bağlı: master'a merge ettikten sonra genelde artık edit yapmazsın ama git status / git log / bin/rails test kesin çalıştırırsın — herhangi bir komut hook'a "toparlanma zamanı" fark etme fırsatı verir.
auto_started bayrağı kritik. /record-feature pro-launch ile manuel kayda başlayıp birden çok branch ve merge'den geçiyorsan, auto-stop kuralları kaydı feature ortasında öldürür. Manuel kayıtlar asla auto-stop olmaz — sadece /stop-recording.
Claude Code hook'u değil — bu .git/hooks/post-commit:
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel) || exit 0
CLAUDE_PROJECT_DIR="$ROOT" "$ROOT/bin/recording-state" commit || true
recording-state commit commit mesajını tam olarak raw.md'ye ekler:
### Commit 2026-04-16 21:55: `71f38a1`
> Add pricing page and expand account UI (P6 phases 1-2)
>
> Pricing (/pricing):
> - Displays all 6 plans in monthly/yearly grid with Stimulus toggle
> - Anonymous users see Subscribe → sign-in flow
> ...
Commit mesajları neden önemli: onlar feature tam bitmişken ve bağlam eksiksizken senin (ya da Claude'un) elle yazdığı özet. Sonra hatırlamaktan daha doğru, transcript'ten kısa, diff'ten soyut.
Claude commit mesajı yazdığında, aslında gelecekteki yazın için özet malzemesi yazıyor demektir — onu baştan iyi yazdırman yeter, sonra yeniden yazmana gerek kalmaz.
"Stop": [{
"hooks": [{ "command": "$CLAUDE_PROJECT_DIR/bin/extract-session-notes" }]
}]
Session bittiğinde Claude Code transcript_path'i hook'a stdin JSON üzerinden geçer. extract-session-notes o jsonl'i açar, satır satır parse eder ve kategorize eder:
keep_patterns = (
"test", "spec", "rspec", "minitest",
"kamal", "git commit", "git push",
"rails db", "rails routes", "rails runner",
"migrate", "curl -X", "curl -s -X",
)
if any(kw in cmd for kw in keep_patterns):
bash_cmds.append({"cmd": cmd[:400], "desc": desc})
Sadece whitelist'teki bash komutları yakalanır. Kod oturumundaki bash'in %90'ı ls / cat / grep / head — yazılar için kullanılmaz, hepsi filtrelenir. Kalanlar (test koşmak, kamal, rails runner, API'lere curl) hikayesi olan komutlardır.
<command-*> veya <system-*> ile sarılmış kullanıcı prompt'ları atılır — sadece gerçek girdi kalır. Edit/Write yolları set'te dedupe edilir. Hata çıktısı 400 karakterde kesilir. Task alt-agent çağrıları korunur (prompt 2000 karaktere kesilir, alt-agent'ın ne yaptığını görmeye yeter).
Stop hook kayıt başına bir kez tetiklenmez — her session sonunda tetiklenir. Aynı feature branch'inde Claude Code'u beş altı kez açıp kapatabilirsin. Her Stop tüm transcript'i yeniden parse edip yazarsa, raw.md tekrarlarda boğulur.
Çözüm: state dosyasına bir last_extracted_at cursor koy:
filter_after = state.get("last_extracted_at") or state.get("started_at")
events = extract_events(transcript_path, filter_after)
# ...yazdıktan sonra...
state["last_extracted_at"] = datetime.now().astimezone().isoformat()
save_state(project_dir, state)
Her seferde sadece cursor sonrası event'leri alır. Basit, ama unutursan yüzlerce duplicate satır düşer.
/record-feature NAME:
{
"feature": "NAME",
"started_at": "ISO timestamp",
"branch": "current branch",
"auto_started": false
}
auto_started: false satırı auto-stop'u devre dışı bırakır. Kullanım: master'da tek seferlik script yazıyorsun ama iz bırakmak istiyorsun, birden çok branch'e yayılan feature, veya açıkça "bu bir yazı için" demek istiyorsun.
/stop-recording: en son jsonl'e karşı son bir extraction çalıştırır, sonra state dosyasını temizler.
Gerçek parça (docs/notes/pro/raw.md'den):
## Session 2026-04-16 21:52 (`7a81bf9d`)
### User prompts
- 根据环境变量直接写好对应只,不用传。
### Files edited/written
- `config/deploy.yml`
- `config/initializers/x402.rb`
### Commit 2026-04-17 00:13: `f87ea8e`
> Add production credentials (Stripe live + x402 mainnet)
> ...
### Commit 2026-04-17 00:57: `eba9ac9`
> Fix production x402 wallet_address (stray fullwidth '?' at end)
Yazı yazarken: sadece grep. "Cüzdan adres bug'ını fixleyen commit hangisiydi?" → eba9ac9. "Refactor alt-agent'ını nasıl prompt'ladım?" → ### Sub-agent invocations altında.
docs/notes/ .gitignore'da — yazın için taslak malzeme, kaynak kodu değil.
1. Branch feature sınırı olarak, ayrı bir metadata sistemi değil. Dev'ler zaten işi branch'e bölüyor; "feature adı" diye başka bir katman eklemek kaçınılmaz drift'e yol açar. Mevcut sınırı yeniden kullan, sıfır bilişsel yük.
2. Bash için whitelist, blacklist değil. Transcript'teki bash'in %95'i gürültü. "Saklamaya değer" listesi yıllarca stabil kalır; "filtrelenecek" listesi kalmaz.
3. Cursor state dosyasında yaşar, transcript'te değil. Transcript Claude Code'a aittir; state pipeline'a aittir. Decoupled — Claude Code transcript formatını değiştirebilir beni bozmadan, ben extraction mantığını değiştirebilirim transcript'e dokunmadan.
1500 kelimelik bir yazıyı yazmak yaklaşık 30 commit, 4 session, on küçük kritik bash komutu malzemesi tüketir. Manuel toplamak yaklaşık bir saat — bir sonraki sefer köşe kesmene yetecek kadar.
Hook'lar yapsın: marjinal maliyet sıfır. Bunu bir kez kur ve sadece normal çalış — branch çek, kod yaz, deploy et — yazı malzemesi kendiliğinden birikir.
Bu yazıdaki her commit referansı, bash parçacığı, hata satırı ve dosya yolu docs/notes/pro/raw.md'den geldi — hook'un iki gün önce feature/pro'yu çektiğim andan itibaren kendisi kaydettiği taslak malzeme. Yazma zamanı geldiğinde dosyayı açtım ve ihtiyacım olan her şey oradaydı.
Claude'a Claude Code hakkında yazı yazdırmanın en iyi yolu, önce Claude'a kendisini kaydeden hook'ları yazdırmak.