Free

Перенос session recording Claude Code на второй проект

Систему записи how2claude переносим в другой Rails-проект: 4 файла, 5 шагов, слои hook.


В how2claude есть набор hooks, которые автоматически записывают сессии Claude Code — стартуют запись при начале работы, добавляют checkpoint на каждом commit, а в конце сессии извлекают prompts / bash / списки правок в docs/notes/<feature>/raw.md. Статья let-claude-record-itself рассказывала, как это собрано.

Проблема: у моего другого проекта (smarts, сайт документации для смарт-контрактов) этого не было. Каждый раз, когда хотелось потом написать статью, я рылся в git log и памяти, чувствуя, что теряю самое сочное. Эта статья — про перенос системы записи туда: всего 4 файла, 5 минут — но по пути всплыла настоящая мысль про слои hooks: hooks Claude Code и hooks git работают на совершенно разных слоях, и что именно захватывается при смешивании инструментов (например, Amp + Claude Code), зависит от того, на каком слое ты поставил.


4 файла, одна картинка

Всё, что в how2claude связано с записью:

Файл Слой Роль
bin/recording-state скрипт Python helper, управляет жизненным циклом .state.json
bin/extract-session-notes скрипт Python helper, читает transcript Claude Code → пишет в raw.md
.claude/settings.local.json hook Claude Code PostToolUse / Stop триггерят скрипты выше
.git/hooks/post-commit hook git каждый commit зовёт recording-state commit для checkpoint
.gitignore контроль шума держит docs/notes/ вне репо (заметки приватные/временные)

4 части, 4 разных слоя. Это различение повторится ниже.

Ключевая мысль: слои hooks решают, что захватывается

Две системы hooks работают одновременно, но с очень разными scope:

Hooks Claude Code (описаны в .claude/settings.local.json):
- Scope: срабатывают только внутри инструмента Claude Code
- Триггеры: PostToolUse / Stop / PreToolUse — события жизненного цикла Claude Code
- Доступная инфа: имя tool, аргументы, transcript_path (полный jsonl сессии) — вещи, известные только Claude Code

Hooks git (shell-скрипты под .git/hooks/):
- Scope: срабатывают на всех git-событиях, не важно, кто инициировал git
- Триггеры: post-commit / pre-push / и т. д.
- Доступная инфа: то, что знает сам git (sha, автор, branch, diff)

Реальное следствие: пишешь код + commit внутри Claude Code — срабатывают оба слоя, инфа сессии и инфа commit попадают в raw.md. Переключаешься на Amp (или Cursor, или руками) для write + commit — срабатывает только git hook — raw.md получает скелет commit, но ни одного prompt / bash / детали правок сессии.

Это не баг — это дизайн-ограничение каждого инструмента. Хочешь деталь уровня сессии под любым инструментом — устанавливай свой слой hook под каждый инструмент. Сеть безопасности git даёт тебе «что было сделано»; не даёт «что было задумано, где сломалось».

Ориентир для выбора:
- Инструмент-агностичный скелет (инфа commit, изменения кода) → в git hook
- Специфичное для Claude Code мясо (полные prompt, рассуждение) → в Claude Code hook
- И то, и другое → ставить на обоих слоях

Перенос в 5 движений

Установка той же схемы записи в smarts (/home/bob/Work/smarts, Rails-проект).

1. Скопировать два Python-скрипта

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}

Скрипты без правок — они используют env-переменную $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 автоматически при срабатывании hooks; мы вручную выставляем её внутри git post-commit. Обе стороны чтут одну env-переменную, и скрипту не нужно решать «в каком я проекте».

2. Создать .claude/settings.local.json

Одно решение здесь: переносим только hooks, не список permissions.

В settings.local.json у how2claude 100+ записей permissions.allow — все специфичны для 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 строг: останавливает только когда «авто-старт И вернулись на master И tree clean» одновременно)
- Сессия завершилась → извлечение transcript в raw.md

3. Создать .git/hooks/post-commit

3 строки:

#!/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 соединяются одной env-переменной. || true гарантирует, что hook никогда не блокирует commit.

chmod +x /home/bob/Work/smarts/.git/hooks/post-commit

4. Добавить docs/notes/ в .gitignore

# Session recording notes (transient, for article material)
docs/notes/

Заметки временные + приватные — не хочется, чтобы raw.md попал в PR; не хочется, чтобы .state.json засорял git status. Gitignore у how2claude обходится с этим так же.

5. Smoke-тест (с маленьким сюрпризом)

Сразу после установки вручную дёрнуть 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
}

Скрипт правильно определил, что smarts как раз на feat/contract-to-docs с грязным tree — автоматически стартовал запись, вывел имя feature contract-to-docs из branch feat/contract-to-docs. Логика в скрипте:

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/XX, feature/XX, голый fix-yfix-y, master/main → None (откатывается на имя с таймстемпом session-YYYYMMDD-HHMM).

Эвристика нарочно туповатая в нужной мере — имя branch и есть тема работы, нет нужды заставлять тебя давать имя снова.

Граница видимости при миксе с Amp

После установки я спросил себя: иногда работаю в Amp — та часть захватится? Ответ ровно такой, как предсказывает история про слои hook:

Сценарий Hook Claude Code Hook git Что попадает в заметки
Работа в Claude Code + commit ✅ срабатывает ✅ срабатывает детали сессии + скелет commit
Работа в Amp + commit ❌ no-op ✅ срабатывает только скелет commit
Ручной набор + commit ❌ no-op ✅ срабатывает только скелет commit
Работа в Claude Code, commit пока нет ✅ стартует запись детали сессии (запись commit ждёт следующего commit)

Вывод: хватает, если Claude Code — главный водитель. Скелет commit попадает всегда; детали сессии — только по маршруту Claude Code. Для статей «что было сделано» в основном опираешься на body commit — prompt / bash-следы сессии — бонус, хорошо иметь, но не обязательно.

Если Amp используется плотно, у Amp своя система hook (в детали не углублялся); маленький скрипт-прокладка, триггерящий recording-state maybe-start/maybe-stop, сработает так же.

Чек-лист

Перенос session recording Claude Code в другой проект — 5 движений:

  1. cp двух Python-скриптов в bin/ целевого проекта. Без правок — скрипты уважают $CLAUDE_PROJECT_DIR, по дизайну переносятся между проектами.
  2. Создать .claude/settings.local.json, только hooks. Не переноси список permissions — permissions это состояние проекта (разное у каждого); hooks — паттерны (одинаковые у всех).
  3. Создать .git/hooks/post-commit (3 строки), вручную export CLAUDE_PROJECT_DIR=$ROOT и вызов recording-state commit. Это единственное место, где мир git и мир Claude Code соединяются одной env-переменной.
  4. Добавить docs/notes/ в .gitignore. Заметки временные + приватные, не часть репо.
  5. Ручной smoke-тест: CLAUDE_PROJECT_DIR=$(pwd) ./bin/recording-state maybe-start, проверить, что вывод branch→feature верен. Если tree clean, скрипт по дизайну пропускает — это не баг.

Настоящее дизайн-решение не «как перенести» — перенос это почти cp. Оно в том, чтобы разрезать логику записи на 4 разных слоя, каждый из которых делает одну ясную вещь:

  • Python-скрипты: helper без состояния, уважает CLAUDE_PROJECT_DIR
  • Hook Claude Code: события внутри инструмента (мясо сессии)
  • Hook git: инструмент-агностичные события (скелет commit)
  • gitignore: контроль шума

Каждый из 4 слоёв можно независимо переносить, заменять или пропускать (нужна поддержка Amp? добавь Amp-слой. Поменять формат заметок? правь Python. Не хочешь, чтобы git отслеживал заметки? удали post-commit). Claude может написать код правильно — но суждение «к какому слою относится эта функциональность» он за тебя не вынесет. Это суждение о границах твоего инструментария, и оно твоё.