athena-update — обновить живую систему безопасно
Антипод
setup-os(ставит с нуля). Здесь система УЖЕ есть — задача подтянуть новую версию каркаса, не затерев личное. Дирижируешь ТЫ;bootstrap.shостается тупым идемпотентным движком.
Принцип
Четыре фазы, без пропусков: Анализ → План → Согласование → Действие. Каждое изменение живой системы — за подтверждением попапом. Бэкап ДО, smoke ПОСЛЕ, откат всегда под рукой. Caveman+humanizer. Структура ответа: что сделано · что нужно · следующий шаг.
Правила НИКОГДА здесь: не chezmoi apply вслепую (всегда diff+согласие — E7) · не auto-merge чужих локальных правок репо (только --ff-only) · не трогать секреты (athena.config.sh, ~/.secrets — gitignored, мимо апдейта) · не обновлять без бэкапа ~/.claude.
Фаза 1 — Анализ (молча, до вопросов)
База сравнения = ~/.claude/.athena-version (SHA, на котором стоит система) ИЛИ git -C ~/athena rev-parse HEAD, если стампа нет.
cd "$HOME/athena" || { echo "нет ~/athena — это setup-os, не update"; exit 1; }
git fetch origin main 2>&1
BASE="$(cat "$HOME/.claude/.athena-version" 2>/dev/null || git rev-parse HEAD)"
git log --oneline "$BASE..origin/main" # что нового
git diff --stat "$BASE" origin/main # какие файлы тронуты
git status -s # локальные правки репо (форк/ручные)
# chezmoi sourceDir ДОЛЖЕН резолвить merged-source (athena-merged-source), не дефолт ~/.local/share/chezmoi.
# Иначе diff ложно-пуст → «нет дрейфа», а apply бьет в пустой source. Чинит bootstrap (config), но проверь:
if command -v chezmoi >/dev/null; then
SP="$(chezmoi source-path 2>/dev/null)"
case "$SP" in *athena-merged-source) chezmoi diff 2>/dev/null | head -100 ;;
*) echo "⚠ chezmoi source-path='$SP' ≠ merged-source — конфиг не запечен, перезапусти bootstrap --only=1 ПЕРЕД apply" ;;
esac
fi
Классифицируй изменения по областям (что меняется → что делать):
| Область | Признак в diff | Действие апдейта | Риск |
|---|---|---|---|
| Движок/слои | bootstrap.sh, Brewfile, *.manifest |
пере-прогон затронутых слоев (идемпотент) | новые brew install, новые клоны |
| Сознание/дотфайлы | chezmoi/** |
chezmoi diff → apply с согласия |
перезатереть живые правки settings.json |
| Skills/правила | skills/**, rules/** |
копия в ~/.claude/ если оттуда читается |
низкий |
| Smoke/CI | smoke/**, .github/** |
просто прогнать после | нет |
| Фиксы безопасности | commit fix:/security: |
приоритет — применить первым | — |
Засеки два флага риска:
- Локальные правки репо (
git statusнепусто / ветка разошлась) →--ff-onlyупадет. НЕ форсить — вынести в План как блокер, дать выбор (stash/ветка/отмена). - Дрейф живого Сознания (
chezmoi diffнепусто) → у юзера есть ручные правки~/.claude, которые apply сотрет. Показать их в Плане пофайлово.
Фаза 2 — План (показать, не делать)
Собери таблицу изменений человеку: область · что нового (1 строка из commit) · действие · риск. Явно подсветь:
- сколько коммитов позади, есть ли среди них
fix:/security:; - что перезапишется в живом
~/.claude(изchezmoi diff) — пофайлово; - блокеры (разошедшийся репо, untracked-правки).
Если позади 0 коммитов И chezmoi diff пуст → «все актуально», стоп.
Фаза 3 — Согласование (попапы, никаких тихих дыр)
- Бэкап (всегда, без вопроса делаешь молча в Фазе 4). Сообщи что будет:
~/.claude→~/.claude.bak-<дата>. - AskUserQuestion multiSelect: «Что обновляем? (что не выберешь — пропустим)» → области из Плана где есть изменения.
- Если есть дрейф Сознания — отдельный попап: «Живые правки
~/.claude/Xбудут перезаписаны новой версией. Как быть?» → [Перезаписать (apply)] [Сохранить мои (skip файла)] [Показать diff целиком]. - Если репо разошелся — попап: [Stash мои правки → обновить → вернуть] [Отдельная ветка] [Отмена].
Gate полноты: каждая область = обновлена ИЛИ явный skip-с-причиной. Без молчаливых пропусков.
Фаза 4 — Действие (безопасно, идемпотентно)
cd "$HOME/athena"
# 1. Бэкап живого Сознания (откат = вернуть папку)
cp -a "$HOME/.claude" "$HOME/.claude.bak-$(date +%Y%m%d-%H%M%S)"
# 2. Подтянуть репо — ТОЛЬКО ff (без auto-merge чужих правок)
git pull --ff-only origin main # упало → вернись к Фазе 3 блокеру (stash/ветка)
# 3. Прогон затронутых слоев: сперва dry-run (покажи), потом реально
./bootstrap.sh --only=<слой> --dry-run # показать
./bootstrap.sh --only=<слой> # применить (по согласию)
# 4. Сознание: diff → apply (НЕ вслепую)
chezmoi diff # показать
chezmoi apply # по согласию; per-file: chezmoi apply ~/.claude/<file>
# 5. Smoke-гейт
smoke/smoke.sh
# 6. Стамп новой версии
git rev-parse HEAD > "$HOME/.claude/.athena-version"
Порядок: фиксы безопасности → движок (слои) → Сознание (chezmoi) → skills/rules → smoke. После каждого шага — короткий статус.
Откат (если smoke упал или что-то не так):
rm -rf "$HOME/.claude" && mv "$HOME/.claude.bak-<дата>" "$HOME/.claude" # вернуть Сознание
git -C "$HOME/athena" reset --hard "$BASE" # вернуть репо
Сообщи человеку обе команды ДО действия (чтобы знал выход).
Финал
- Покажи: было SHA → стало SHA, какие области обновлены, какие пропущены-с-причиной.
- Smoke OK? Бэкап где лежит (можно удалить через N дней).
- Применил
fix:/security:— назови их явно. - Предложи удалить старые
~/.claude.bak-*если их >2.