Sistem rekam how2claude ke proyek Rails lain: 4 file, 5 langkah, hook layering.
how2claude punya satu set hooks yang otomatis ngerekam session Claude Code — mulai rekam pas kerjaan dimulai, nambahin checkpoint di tiap commit, dan pas session selesai, nge-ekstrak prompt / bash / daftar edit ke docs/notes/<feature>/raw.md. Artikel let-claude-record-itself udah bahas gimana dibangunnya.
Masalahnya: proyek gue satunya (smarts, situs docs untuk smart contract) gak punya apa-apa dari ini. Tiap mau nulis artikel belakangan, gue bongkar git log plus ingatan dan ngerasa ada yang kelewat. Artikel ini cerita gimana mindahin sistem rekam ke sana — 4 file, 5 menit total — tapi di jalan muncul satu insight real soal hook layering: hooks Claude Code dan hooks git beroperasi di layer yang sama sekali beda, dan apa yang ketangkep pas lo campur tool (Amp + Claude Code, misalnya) tergantung layer mana lo pasangnya.
Semua di how2claude yang nyangkut rekaman:
| File | Layer | Peran |
|---|---|---|
bin/recording-state |
script | Python helper, ngelola lifecycle .state.json |
bin/extract-session-notes |
script | Python helper, baca transcript Claude Code → nulis raw.md |
.claude/settings.local.json |
hook Claude Code | PostToolUse / Stop ngetrigger script di atas |
.git/hooks/post-commit |
hook git | tiap commit manggil recording-state commit buat checkpoint |
.gitignore |
kontrol noise | jaga docs/notes/ gak masuk repo (catatan itu privat/transient) |
4 potongan, 4 layer beda. Pembedaan ini muncul balik terus di bawah.
Dua sistem hook jalan bareng, tapi scope-nya jauh beda:
Hooks Claude Code (didefinisiin di .claude/settings.local.json):
- Scope: cuma nyala di dalam tool Claude Code
- Trigger: PostToolUse / Stop / PreToolUse — event lifecycle Claude Code
- Info tersedia: nama tool, args, transcript_path (jsonl session lengkap) — barang yang cuma Claude Code tau
Hooks git (script shell di bawah .git/hooks/):
- Scope: nyala di tiap event git, gak peduli siapa yang ngetrigger git-nya
- Trigger: post-commit / pre-push / dll.
- Info tersedia: apa yang git sendiri tau (sha, author, branch, diff)
Konsekuensi nyata: nulis kode + commit di dalam Claude Code ngetrigger dua layer sekaligus — info session dan info commit dua-duanya mendarat di raw.md. Ganti ke Amp (atau Cursor, atau ngetik tangan) buat nulis + commit dan cuma hook git yang nyala — raw.md dapet kerangka commit tapi gak ada detail prompt / bash / edit dari session.
Ini bukan bug — ini constraint desain masing-masing tool. Mau detail level session di bawah semua tool berarti pasang hook layer lo sendiri per tool. Fallback git kasih lo "apa yang dikerjain"; gak kasih lo "apa yang dipikirin, apa yang rusak".
Panduan pemilihan:
- Kerangka tool-agnostic (info commit, perubahan kode) → taruh di hook git
- Daging khas Claude Code (prompt lengkap, proses pikir) → taruh di hook Claude Code
- Dua-duanya → pasang di dua layer
Pasang setup rekam yang sama di smarts (/home/bob/Work/smarts, proyek Rails).
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 tanpa modifikasi — pake env var $CLAUDE_PROJECT_DIR buat nentuin nulis ke mana:
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"
Ini abstraksi kunci: Claude Code nge-set CLAUDE_PROJECT_DIR otomatis pas ngetrigger hook; kita nge-set manual di dalam post-commit git. Dua sisi ngormatin env var yang sama, dan script gak perlu ngerti "gue lagi di proyek mana".
.claude/settings.local.jsonSatu keputusan di sini: cuma mindahin hooks, bukan daftar permissions.
settings.local.json how2claude punya 100+ entri permissions.allow — semuanya spesifik how2claude (curl localhost:3000, bin/rails runner, kamal app exec). Gak ada artinya bawa ke smarts. smarts bakal nimbun permissions sendiri organik selama dipake.
Hooks itu pola — identik antar proyek. Permissions itu state proyek — beda antar proyek.
{
"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" }
]
}
]
}
}
Tiga titik trigger:
- File diedit → coba mulai rekam (maybe-start self-check: kalo udah rekam, skip; kalo tree clean, juga skip)
- Command bash dijalanin → coba stop (maybe-stop ketat: cuma stop kalo "auto-started DAN balik ke master DAN tree clean" semua kepenuhan)
- Session selesai → ekstrak transcript ke raw.md
.git/hooks/post-commit3 baris:
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
CLAUDE_PROJECT_DIR="$ROOT" "$ROOT/bin/recording-state" commit || true
Export manual CLAUDE_PROJECT_DIR=$ROOT itu titik persis di mana dunia git dan dunia Claude Code dijembatanin pake env var yang sama. || true jamin hook gak pernah ngeblok commit.
chmod +x /home/bob/Work/smarts/.git/hooks/post-commit
docs/notes/ ke .gitignore# Session recording notes (transient, for article material)
docs/notes/
Catatan itu transient + privat — lo gak pengen raw.md di-commit ke PR; lo gak pengen .state.json kotorin git status. gitignore how2claude juga begitu.
Trigger maybe-start manual langsung abis install:
$ 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 bener-bener ngenalin smarts kebetulan di branch feat/contract-to-docs dengan tree dirty — auto-mulai rekam, nyimpulin nama feature contract-to-docs dari branch feat/contract-to-docs. Logika itu di script:
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, fix-y polos → fix-y, master/main → None (yang fallback ke nama timestamp session-YYYYMMDD-HHMM).
Heuristik-nya sengaja dudul-dudul pas — nama branch adalah subjek kerjaan, gak perlu lo kasih nama lagi.
Abis pasang gue nanya diri: kadang-kadang gue kerja di Amp — itu ketangkep? Jawabannya persis kayak prediksi cerita hook layering:
| Skenario | Hook Claude Code | Hook git | Apa yang masuk catatan |
|---|---|---|---|
| Kerja Claude Code + commit | ✅ nyala | ✅ nyala | detail session + kerangka commit |
| Kerja Amp + commit | ❌ no-op | ✅ nyala | cuma kerangka commit |
| Ngetik tangan + commit | ❌ no-op | ✅ nyala | cuma kerangka commit |
| Kerja Claude Code, belum commit | ✅ mulai rekam | — | detail session (entry commit nunggu commit berikutnya) |
Intinya: cukup kalo Claude Code jadi driver utama. Kerangka commit selalu masuk; detail session cuma masuk di jalur Claude Code. Buat artikel "apa yang dikerjain" lo utamanya sandarin ke body commit — prompt / jejak bash session itu bonus, enak kalo ada tapi gak wajib.
Kalo lo pake Amp berat, Amp punya sistem hook sendiri (gak gue dalemin); script forwarding kecil yang ngetrigger recording-state maybe-start/maybe-stop bakal jalan sama.
Mindahin session recording Claude Code ke proyek lain — 5 langkah:
cp dua script Python ke bin/ proyek target. Tanpa modifikasi — script ngormatin $CLAUDE_PROJECT_DIR, portable antar proyek by design..claude/settings.local.json, cuma hooks. Jangan pindahin daftar permissions — permissions itu state proyek (beda per proyek); hooks itu pola (identik antar proyek)..git/hooks/post-commit (3 baris), export CLAUDE_PROJECT_DIR=$ROOT manual dan panggil recording-state commit. Itu satu-satunya titik di mana dunia git dan dunia Claude Code dijembatanin pake env var yang sama.docs/notes/ ke .gitignore. Catatan itu transient + privat, bukan bagian dari repo.CLAUDE_PROJECT_DIR=$(pwd) ./bin/recording-state maybe-start, cek inferensi branch→feature bener gak. Kalo tree clean, script didesain buat skip — bukan bug.Keputusan desain beneran bukan "gimana gue mindahin" — mindahin hampir tinggal cp. Tapi bagi logika rekam ke 4 layer beda, masing-masing ngerjain satu hal yang jelas:
CLAUDE_PROJECT_DIRMasing-masing dari 4 layer bisa dipindahin, diganti, atau dilewatin secara independen (mau dukungan Amp? tambahin layer hook Amp. Ubah format catatan? edit Python. Gak mau git ngelacak catatan? hapus post-commit). Claude bisa nulis kode bener — tapi pertimbangan "fungsi ini masuk layer mana" gak bisa dia buatin buat lo. Itu pertimbangan soal batas tooling lo, dan itu lo punya.