ระบบบันทึกของ how2claude ไปโปรเจกต์ Rails อื่น: 4 ไฟล์ 5 ขั้น การแบ่งชั้น hook
how2claude มีชุด hooks ที่บันทึก session ของ Claude Code อัตโนมัติ — เริ่มบันทึกเมื่องานเริ่ม เพิ่ม checkpoint ทุกครั้งที่ commit และเมื่อ session จบจะดึง prompts / bash / รายการ edit ไปที่ docs/notes/<feature>/raw.md บทความ let-claude-record-itself เล่าว่าสร้างมันขึ้นมายังไง
ปัญหา: อีกโปรเจกต์ของผม (smarts เว็บเอกสารสำหรับ smart contract) ไม่มีของพวกนี้เลย ทุกครั้งที่อยากเขียนบทความย้อนหลัง ก็ต้องขุด git log บวกความจำและรู้สึกว่าพลาดของดี บทความนี้เล่าเรื่องการย้ายระบบบันทึกไปที่นั่น — รวม 4 ไฟล์ 5 นาที — แต่ระหว่างทาง โผล่ insight จริงเรื่องการแบ่งชั้น hook: hooks ของ Claude Code กับ hooks ของ git ทำงานกันคนละชั้นโดยสิ้นเชิง และอะไรจะถูกจับเมื่อคุณผสมเครื่องมือ (เช่น Amp + Claude Code) ขึ้นอยู่กับว่าคุณติดตั้งที่ชั้นไหน
ทุกอย่างใน how2claude ที่เกี่ยวกับการบันทึก:
| ไฟล์ | ชั้น | บทบาท |
|---|---|---|
bin/recording-state |
script | Python helper จัดการวงจรชีวิตของ .state.json |
bin/extract-session-notes |
script | Python helper อ่าน transcript ของ Claude Code → เขียนลง raw.md |
.claude/settings.local.json |
hook Claude Code | PostToolUse / Stop ทริกเกอร์ script ด้านบน |
.git/hooks/post-commit |
hook git | ทุก commit เรียก recording-state commit เพื่อ checkpoint |
.gitignore |
ควบคุมเสียงรบกวน | กัน docs/notes/ ออกจาก repo (โน้ตเป็นส่วนตัว/ชั่วคราว) |
4 ชิ้น 4 ชั้นต่างกัน ความแตกต่างนี้จะกลับมาด้านล่าง
สองระบบ hook วิ่งพร้อมกันแต่ scope ต่างกันมาก:
Hooks ของ Claude Code (นิยามใน .claude/settings.local.json):
- Scope: ยิงในเครื่องมือ Claude Code เท่านั้น
- ทริกเกอร์: PostToolUse / Stop / PreToolUse — event วงจรชีวิต Claude Code
- ข้อมูลที่ได้: ชื่อ tool, args, transcript_path (jsonl เต็มของ session) — สิ่งที่มีแต่ Claude Code รู้
Hooks ของ git (shell script ใต้ .git/hooks/):
- Scope: ยิงทุก event ของ git ไม่สำคัญใครเป็นคนกระตุ้น git
- ทริกเกอร์: post-commit / pre-push / ฯลฯ
- ข้อมูลที่ได้: สิ่งที่ git เองรู้ (sha, author, branch, diff)
ผลลัพธ์จริง: เขียนโค้ด + commit ใน Claude Code ทั้งสองชั้นยิง — ข้อมูล session และข้อมูล commit ลงใน raw.md ทั้งคู่ สลับไป Amp (หรือ Cursor หรือพิมพ์มือ) เพื่อเขียน + commit ยิงแค่ hook ของ git — raw.md ได้โครง commit แต่ไม่มี prompt / bash / รายละเอียด edit ของ session
ไม่ใช่บั๊ก — เป็นข้อจำกัดการออกแบบของแต่ละเครื่องมือ อยากได้รายละเอียดระดับ session ใต้ทุกเครื่องมือ = ต้องติดตั้งชั้น hook ของตัวเองในแต่ละเครื่องมือ ตาข่ายสำรองของ git ให้ "ทำอะไรไป" แต่ไม่ให้ "คิดอะไร พังตรงไหน"
คู่มือการเลือก:
- โครง ไม่ขึ้นกับเครื่องมือ (ข้อมูล commit, การเปลี่ยนโค้ด) → ใส่ใน git hook
- เนื้อ เฉพาะ Claude Code (prompt เต็ม, กระบวนการคิด) → ใส่ใน Claude Code hook
- ทั้งสองอย่าง → ติดตั้งทั้งสองชั้น
ติดตั้งการตั้งค่าบันทึกแบบเดียวกันใน smarts (/home/bob/Work/smarts, โปรเจกต์ 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 ไม่ต้องแก้ — ใช้ตัวแปรสภาพแวดล้อม $CLAUDE_PROJECT_DIR ตัดสินว่าจะเขียนที่ไหน:
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"
นี่คือ นามธรรมหลัก: Claude Code ตั้ง CLAUDE_PROJECT_DIR อัตโนมัติเมื่อยิง hook เราตั้งมันด้วยมือใน git post-commit ทั้งสองฝั่งเคารพตัวแปรสภาพแวดล้อมเดียวกัน และ script ไม่ต้องหาว่า "ฉันอยู่โปรเจกต์ไหน"
.claude/settings.local.jsonการตัดสินใจหนึ่ง: ย้ายแค่ hooks ไม่ย้ายรายการ permissions
settings.local.json ของ how2claude มี permissions.allow กว่า 100 รายการ — ทั้งหมดเฉพาะ how2claude (curl localhost:3000, bin/rails runner, kamal app exec) ไร้ความหมายถ้าลากไป smarts smarts จะสะสม permissions ของตัวเองแบบธรรมชาติขณะใช้
Hooks คือ แพทเทิร์น — เหมือนกันข้ามโปรเจกต์ Permissions คือ สถานะโปรเจกต์ — ต่างกันข้ามโปรเจกต์
{
"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" }
]
}
]
}
}
สามจุดทริกเกอร์:
- ไฟล์ถูกแก้ → ลองเริ่มบันทึก (maybe-start เช็คตัวเอง: ถ้าบันทึกอยู่แล้วข้าม, ถ้า tree clean ก็ข้าม)
- คำสั่ง bash รัน → ลองหยุด (maybe-stop เข้มงวด: หยุดเมื่อ "auto-start และกลับมา master และ tree clean" ครบสามข้อพร้อมกัน)
- Session จบ → ดึง transcript ลง raw.md
.git/hooks/post-commit3 บรรทัด:
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
CLAUDE_PROJECT_DIR="$ROOT" "$ROOT/bin/recording-state" commit || true
การ export CLAUDE_PROJECT_DIR=$ROOT ด้วยมือคือจุดเป๊ะที่โลกของ git กับโลกของ Claude Code ถูกเชื่อมด้วยตัวแปรสภาพแวดล้อมเดียวกัน || true รับประกันว่า hook จะไม่มีวันบล็อก commit
chmod +x /home/bob/Work/smarts/.git/hooks/post-commit
docs/notes/ ใน .gitignore# Session recording notes (transient, for article material)
docs/notes/
โน้ต ชั่วคราว + ส่วนตัว — ไม่อยากให้ raw.md ถูก commit เข้า PR ไม่อยากให้ .state.json เลอะ git status gitignore ของ how2claude จัดการแบบเดียวกัน
ทริกเกอร์ maybe-start ด้วยมือทันทีหลังติดตั้ง:
$ 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 บังเอิญอยู่ที่ branch feat/contract-to-docs กับ tree dirty — เริ่มบันทึกอัตโนมัติ อนุมานชื่อ feature contract-to-docs จาก branch feat/contract-to-docs ตรรกะใน 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 โล้น → fix-y, master/main → None (ตกกลับไปชื่อ timestamp session-YYYYMMDD-HHMM)
heuristic โง่เป๊ะในระดับที่พอดี — ชื่อ branch คือ หัวข้องาน ไม่ต้องบังคับคุณตั้งชื่อซ้ำ
หลังติดตั้งถามตัวเอง: บางครั้งทำงานใน Amp — ส่วนนั้นถูกจับไหม? คำตอบตรงกับเรื่องการแบ่งชั้น hook ที่พยากรณ์ไว้:
| สถานการณ์ | Hook Claude Code | Hook git | อะไรลงในโน้ต |
|---|---|---|---|
| ทำงาน Claude Code + commit | ✅ ยิง | ✅ ยิง | รายละเอียด session + โครง commit |
| ทำงาน Amp + commit | ❌ no-op | ✅ ยิง | โครง commit เท่านั้น |
| พิมพ์มือ + commit | ❌ no-op | ✅ ยิง | โครง commit เท่านั้น |
| ทำงาน Claude Code ยังไม่ commit | ✅ เริ่มบันทึก | — | รายละเอียด session (รายการ commit รอ commit ถัดไป) |
สรุป: พอใช้ถ้า Claude Code เป็นผู้ขับหลัก โครง commit ลงเสมอ รายละเอียด session ลงเฉพาะเส้นทาง Claude Code สำหรับบทความประเภท "ทำอะไรไป" คุณพึ่ง body commit เป็นหลัก prompt / ร่องรอย bash ของ session เป็นโบนัส — มีก็ดีแต่ไม่ต้องมี
ถ้าใช้ Amp หนัก Amp มีกลไก hook เอง (ไม่ได้เจาะรายละเอียด) script ส่งต่อเล็กๆ ที่ทริกเกอร์ recording-state maybe-start/maybe-stop ใช้หลักการเดียวกัน
ย้าย session recording ของ Claude Code ไปโปรเจกต์อื่น — 5 ก้าว:
cp script Python สองตัว ไป bin/ ของโปรเจกต์เป้าหมาย ไม่ต้องแก้ — script เคารพ $CLAUDE_PROJECT_DIR พกพาข้ามโปรเจกต์ได้ตามดีไซน์.claude/settings.local.json hooks เท่านั้น อย่าย้ายรายการ permissions — permissions คือสถานะโปรเจกต์ (ต่างกันต่อโปรเจกต์) hooks คือแพทเทิร์น (เหมือนกันข้ามโปรเจกต์).git/hooks/post-commit (3 บรรทัด) export CLAUDE_PROJECT_DIR=$ROOT ด้วยมือและเรียก recording-state commit นี่คือจุดเดียวที่โลก git และโลก Claude Code ถูกเชื่อมด้วยตัวแปรสภาพแวดล้อมเดียวกันdocs/notes/ ใน .gitignore โน้ตชั่วคราว + ส่วนตัว ไม่ใช่ส่วนของ repoCLAUDE_PROJECT_DIR=$(pwd) ./bin/recording-state maybe-start ตรวจว่าการอนุมาน branch→feature ถูกต้อง ถ้า tree clean script ถูก ออกแบบ ให้ข้าม — ไม่ใช่บั๊กการตัดสินใจด้านดีไซน์จริงๆ ไม่ใช่ "ย้ายยังไง" — ย้ายเกือบจะเป็น cp มันคือการ แบ่งตรรกะการบันทึกเป็น 4 ชั้นต่างกัน แต่ละชั้นทำหน้าที่หนึ่งชัดเจน:
CLAUDE_PROJECT_DIRทั้ง 4 ชั้นย้าย เปลี่ยน หรือข้ามได้อย่างอิสระ (อยากได้ support Amp? เพิ่มชั้น hook ของ Amp เปลี่ยนรูปแบบโน้ต? แก้ Python ไม่อยากให้ git ติดตามโน้ต? ลบ post-commit) Claude เขียนโค้ดถูกต้องได้ — แต่การตัดสิน "ฟังก์ชันนี้อยู่ชั้นไหน" เขาทำแทนคุณไม่ได้ นั่นเป็นการตัดสินเรื่องขอบเขตเครื่องมือของคุณ และเป็นของคุณ