הכי קשה במאמרי מקרה אמיתי זה לאסוף חומר: git log מחזיק רק commit messages, פרטי הסשן נעלמים. ארבעה hooks ושני slash commands הופכים כל סשן ל-raw.md — עלות שולית אפס.
החלק הכי קשה בכתיבת מאמר "Claude עשה עבורי X" הוא לא הכתיבה — זה איסוף החומר. git log מחזיק רק commit messages; הגישות שדחית, הניסיונות הכושלים, שלוש הגרסאות של אותו prompt, פלט השגיאה — זה מה שהופך מאמר לשווה קריאה, והכל אבד.
גרוע יותר: כשאתה כבר כותב, אתה לא זוכר למה בחרת ב-A ולא ב-B. "נדמה לי שהייתה סיבה" מול "אותה סיבה הייתה X" — זה ההבדל בין מאמר אמין למילוי מקום.
רישום פרוטוקולים עולה יותר מכתיבת המאמר, אז אתה לא רושם. הפתרון היחיד: שההקלטה תקרה לבד — hooks.
גרמתי ל-Claude לכתוב 4 hooks ו-2 slash commands שהופכים כל session פיתוח ל-docs/notes/<feature>/raw.md. המאמר הקודם לתת ל-Claude לפרוס ל-production שאב את רוב ה-commits, שברי bash והפניות לשגיאות מתוך הצינור הזה. גם המאמר הזה.
4 hooks ושני slash commands ידניים יוצרים מסוע אחד:
| טריגר | מה עושה |
|---|---|
| PostToolUse(Edit\ | Write\ |
| PostToolUse(Bash) | עוצר כשחוזרים ל-master + נקי |
| git post-commit | כותב checkpoint לכל commit |
| Stop | מפרסר transcript בסוף הסשן |
/record-feature NAME /stop-recording |
עקיפה ידנית |
קובץ state יחיד: docs/notes/.state.json. כל hook קורא וכותב אליו. אין עוד תיאום.
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/bin/recording-state maybe-start"
}]
}
]
ליבת maybe-start:
def cmd_maybe_start():
if load_state(): return # כבר מקליטים
if not tree_is_dirty(): return # שום דבר לא השתנה, דלג
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". מפתחים כבר מפצלים עבודה לפי branch, אז ממחזרים את אותו גבול מנטלי — הסיבה השורשית לכך שהצינור שורד: הוא לא מבקש מהמשתמש לזכור שום דבר חדש. על master, סקריפטים חד-פעמיים נופלים ל-timestamp.
למה ה-matcher הוא Edit/Write/MultiEdit: כתיבת קוד היא הסימן האמיתי של "אני באמת עושה משהו". קריאת קבצים, הרצת בדיקות, שאילת שאלות — לא נחשב.
{
"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()
שלושה שערים: הקלטות ידניות לא עוצרות, feature branches לא עוצרות, עץ מלוכלך לא עוצר.
למה מקושר ל-Bash ולא ל-Edit: אחרי merge ל-master בדרך כלל כבר לא עורכים, אבל בטוח ירוצו git status / git log / bin/rails test — כל פקודה נותנת ל-hook הזדמנות להבחין ב-"זמן לסגור".
דגל auto_started הוא קריטי. אם תריץ /record-feature pro-launch ידנית ותעבור דרך מספר branches על פני מספר merges, כללי auto-stop יהרגו את ההקלטה באמצע feature. הקלטות ידניות לעולם לא נעצרות אוטומטית — רק /stop-recording.
לא hook של Claude Code — זה .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 המלאה ל-raw.md:
### 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 חשובות: זה הסיכום הכתוב ביד שאתה (או Claude) הפקת בדיוק ברגע שה-feature זה עתה הסתיים וההקשר היה מלא. מדויק יותר מלזכור אחר כך, קצר יותר מה-transcript, מופשט יותר מה-diff.
כש-Claude כותב הודעת commit, הוא בעצם כותב חומר סיכום למאמר העתידי שלך — אתה רק צריך שהוא יעשה את זה טוב בפעם הראשונה, בלי שכתוב אחר כך.
"Stop": [{
"hooks": [{ "command": "$CLAUDE_PROJECT_DIR/bin/extract-session-notes" }]
}]
כשסשן מסתיים, Claude Code מעביר transcript_path ל-hook דרך stdin כ-JSON. extract-session-notes פותח את ה-jsonl הזה, הולך עליו שורה-שורה וממיין:
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})
רק פקודות bash מה-whitelist נשמרות. 90% מה-bash בסשן קוד הוא ls / cat / grep / head — מאמרים לא משתמשים בזה, הכל מסונן. מה שנשאר (הרצת בדיקות, kamal, rails runner, curl מול APIs) אלו הפקודות עם סיפור מאחור.
prompts של משתמש עטופים ב-<command-*> או <system-*> נזרקים — רק הקלט האמיתי שורד. נתיבי Edit/Write מעובדים לסט ללא כפילויות. פלט שגיאות חתוך ל-400 תווים. קריאות Task sub-agent נשמרות (prompt חתוך ל-2000 תווים, מספיק לראות מה ה-sub-agent עשה).
ה-Stop hook לא יורה פעם אחת להקלטה — הוא יורה בכל פעם שסשן מסתיים. על אותו feature branch אתה יכול לפתוח ולסגור את Claude Code חמש-שש פעמים. אם כל Stop היה מפרסר מחדש את כל ה-transcript וכותב הכל, raw.md היה טובע בכפילויות.
פתרון: לשים cursor last_extracted_at בקובץ ה-state:
filter_after = state.get("last_extracted_at") or state.get("started_at")
events = extract_events(transcript_path, filter_after)
# ...אחרי הכתיבה...
state["last_extracted_at"] = datetime.now().astimezone().isoformat()
save_state(project_dir, state)
כל מעבר לוקח רק אירועים אחרי ה-cursor. פשוט, אבל תשכח — ותקבל מאות שורות כפולות.
/record-feature NAME:
{
"feature": "NAME",
"started_at": "ISO timestamp",
"branch": "current branch",
"auto_started": false
}
השורה auto_started: false מבטלת את auto-stop. מקרי שימוש: לכתוב סקריפט חד-פעמי על master אבל עדיין להשאיר עקבות; feature שחוצה מספר branches; להצהיר במפורש "זה למאמר".
/stop-recording: מריץ extraction סופי מול ה-jsonl העדכני ביותר, ואז מנקה את קובץ ה-state.
קטע אמיתי (מתוך docs/notes/pro/raw.md):
## 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)
לכתיבת מאמר: פשוט grep. "איזה commit היה fix של wallet address?" → eba9ac9. "איך prompt-תי את sub-agent ה-refactor?" → תחת ### Sub-agent invocations.
docs/notes/ ב-.gitignore — זה חומר טיוטה לכתיבה שלך, לא קוד מקור.
1. Branch כגבול של feature, לא מערכת metadata נפרדת. מפתחים כבר מחלקים עבודה לפי branch; הוספת שכבה נוספת של "שם feature" תסטה בהכרח. ממחזרים את הגבול הקיים, אפס עומס קוגניטיבי.
2. Whitelist ל-bash, לא blacklist. 95% מה-bash ב-transcripts הוא רעש. שמירה על רשימת "מה שווה לשמור" יציבה שנים; שמירה על רשימת "מה לסנן" לא.
3. ה-cursor חי בקובץ ה-state, לא ב-transcript. ה-transcript שייך ל-Claude Code; ה-state שייך ל-pipeline. מפוצלים — Claude Code יכול לשנות פורמט transcript בלי לשבור אותי, ואני יכול לשכתב את לוגיקת החילוץ בלי לגעת ב-transcripts.
כתיבת מאמר של 1500 מילים צורכת חומר של בערך 30 commits, 4 סשנים, תריסר פקודות bash מפתח. לאסוף ביד עולה בערך שעה — מספיק כדי שבפעם הבאה תחתוך פינות.
תן ל-hooks לעשות את זה: עלות שולית אפס. התקן את זה פעם אחת ופשוט עבוד רגיל — משוך branch, כתוב קוד, פרוס — וחומר המאמר מצטבר לבד.
כל הפניה ל-commit, קטע bash, שורת שגיאה ונתיב קובץ במאמר הזה הגיעו מ-docs/notes/pro/raw.md — חומר טיוטה ש-hook הקליט לבד מהרגע שמשכתי feature/pro לפני יומיים. כשהגיע הזמן לכתוב, פתחתי את הקובץ והכל מה שהייתי צריך היה שם.
הדרך הכי טובה לגרום ל-Claude לכתוב מאמרים על Claude Code היא קודם לגרום ל-Claude לכתוב hooks שמקליטים אותו עצמו.