Одна команда — и Claude Code сам анализирует коммиты, определяет тип релиза, генерирует changelog, обновляет версии и пушит в GitHub. Разбираем внутрянку 1000-строчного bash-скрипта.
Скрытый текст
Команда /push — это slash-команда для Claude Code, которая:
Анализирует все коммиты с последнего релиза
Автоматически определяет тип версии (patch/minor/major) по conventional commits
Генерирует CHANGELOG.md в формате Keep a Changelog
Обновляет все package.json в монорепозитории
Создаёт git tag и пушит в remote
Откатывает все изменения при ошибке
Репозиторий: github.com/maslennikov-ig/claude-code-orchestrator-kit (MIT, бесплатно)
Проблема: Релизы — это боль
Каждый разработчик знает этот ритуал:
Посмотреть
git log, чтобы понять, что изменилосьРешить, какой номер версии поставить
Обновить
package.json(а если монорепо — то все 5 штук)Написать CHANGELOG (и забыть половину фич)
Сделать коммит "chore: bump version"
Создать тег
Запушить
Понять, что забыл добавить важный фикс в changelog
Сделать
--amend, удалить тег, пересоздать тег...🤯
В нашем проекте Claude Code Orchestrator Kit релизы случаются 2-3 раза в день. После 50-го "chore: bump version" я решил, что хватит.
Решение: Одна команда — /push
Теперь мой релиз выглядит так:
/push patchИ Claude Code делает всё сам:
╔═══════════════════════════════════════════════════════════╗
║ Release Automation ║
╚═══════════════════════════════════════════════════════════╝
ℹ️ Running pre-flight checks...
✅ On branch: main
✅ Remote configured
✅ Node.js available
✅ Current version: 1.3.4
✅ Last tag: v1.3.4
✅ Found 3 commits since last release
ℹ️ Analyzing commits since v1.3.4...
ℹ️ Commit summary:
✨ 1 features
🐛 1 bug fixes
📝 1 other changes
═══════════════════════════════════════════════════════════
RELEASE PREVIEW
═══════════════════════════════════════════════════════════
📌 Version: 1.3.4 → 1.3.5 (PATCH)
Reason: Manually specified
📦 Package Updates:
✓ package.json
📄 CHANGELOG.md Entry:
───────────────────────────────────────────────────────────
## [1.3.5] - 2025-12-20
### Added
- **agents**: add new security-scanner worker (abc1234)
### Fixed
- **api**: fix authentication timeout issue (def5678)
───────────────────────────────────────────────────────────
✅ Released v1.3.5
✅ Tag: v1.3.5
✅ Pushed to origin/main
Весь процесс занимает 3-5 секунд.
Как это работает под капотом
Архитектура
Команда /push — это slash-команда Claude Code. Она определена в файле .claude/commands/push.md:
---
description: Automated release management with version bumping and changelog updates
argument-hint: [patch|minor|major]
---
Execute the release automation script with auto-confirmation for Claude Code.
**Features:**
- Auto-syncs package.json versions with latest git tag
- Analyzes commits since last release
- Auto-detects version bump type from conventional commits
- Generates CHANGELOG entries
- Updates all package.json files
- Creates git tag and pushes to GitHub
- Full rollback support on errors
Это обёртка над bash-скриптом release.sh на 1050+ строк. Разберём ключевые части.
Фича 1: Auto-commit перед релизом
Проблема: Вы поработали, накопили изменения, хотите релизнуть. Но сначала надо всё закоммитить. А это отдельная история с придумыванием сообщения.
Решение: Скрипт сам коммитит незакоммиченные изменения с умным определением типа:
# Detect commit type based on changed files
local commit_type="chore"
local commit_scope=""
# Get file changes with status
local file_status=$(git diff --cached --name-status)
# Priority-based detection
local new_agents=$(echo "$file_status" | grep "^A.*\.claude/agents/.*\.md$" | wc -l)
local new_skills=$(echo "$file_status" | grep "^A.*\.claude/skills/.*/SKILL\.md$" | wc -l)
local new_commands=$(echo "$file_status" | grep "^A.*\.claude/commands/.*\.md$" | wc -l)
# New agents = feat(agents)
if [ "$new_agents" -gt 0 ]; then
commit_type="feat"
commit_scope="agents"
local agent_name=$(basename "$agent_file" .md)
commit_desc="add ${agent_name} agent"
fi
Как это работает:
Скрипт детектит, какие файлы изменились
По паттернам определяет тип коммита:
Новые агенты →
feat(agents): add security-scanner agentНовые скиллы →
feat(skills): add validate-plan skillИзменения скриптов →
chore(scripts): update automation scriptsДокументация →
docs: update documentation
Автоматически стейджит и коммитит
Результат:
ℹ️ Uncommitted changes detected. Auto-committing before release...
✅ Changes committed (15 files)
ℹ️ Commit type: feat(agents): add 3 new agents (bug-hunter, ...)Фича 2: Синхронизация версий с Git Tags
Про��лема: В монорепозитории легко получить рассинхрон: package.json говорит 1.3.2, а последний тег — v1.3.4. Это приводит к конфликтам при следующем релизе.
Решение: Скрипт сверяет версию в package.json с последним тегом и автоматически синхронизирует:
# Get last git tag
LAST_TAG=$(git tag --sort=-version:refname | safe_first || echo "")
# Sync package.json version with git tag if needed
TAG_VERSION="${LAST_TAG#v}" # Remove 'v' prefix
if [ "$CURRENT_VERSION" != "$TAG_VERSION" ]; then
log_warning "Version mismatch: package.json ($CURRENT_VERSION) != tag ($TAG_VERSION)"
log_info "Syncing package.json versions to $TAG_VERSION..."
# Find and update all package.json files
find "$PROJECT_ROOT" -name "package.json" -not -path "*/node_modules/*" -print0 | \
while IFS= read -r -d '' pkg_file; do
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$TAG_VERSION\"/" "$pkg_file"
done
log_success "Synced all package.json files to version $TAG_VERSION"
fiЭто спасает от ситуации "у меня локально 1.3.2, а на remote уже 1.3.4".
Фича 3: Умный анализ Conventional Commits
Проблема: Определить тип релиза — это не просто "есть фича или нет". Надо учитывать breaking changes, security fixes, deprecations.
Решение: Скрипт парсит все коммиты и категоризирует их по типам:
# Supported conventional commit types
local breaking_pattern='^[a-z]+(\([^)]+\))?!:' # type!: → major
local feat_pattern='^feat(\([^)]+\))?:' # feat: → minor
local fix_pattern='^fix(\([^)]+\))?:' # fix: → patch
local security_pattern='^security(\([^)]+\))?:' # security: → patch (high priority!)
local deprecate_pattern='^deprecate(\([^)]+\))?:' # deprecate: → shown in changelog
local remove_pattern='^remove(\([^)]+\))?:' # remove: → shown in changelog
for commit in "${ALL_COMMITS[@]}"; do
local message=$(echo "$commit" | cut -d' ' -f2-)
if [[ "$message" =~ $breaking_pattern ]]; then
BREAKING_CHANGES+=("$commit")
elif [[ "$message" =~ $security_pattern ]]; then
SECURITY_FIXES+=("$commit")
elif [[ "$message" =~ $feat_pattern ]]; then
FEATURES+=("$commit")
# ... и так далее
fi
doneАвтоопределение версии:
detect_version_bump() {
if [ ${#BREAKING_CHANGES[@]} -gt 0 ]; then
BUMP_TYPE="major" # Breaking changes → major
elif [ ${#FEATURES[@]} -gt 0 ]; then
BUMP_TYPE="minor" # New features → minor
elif [ ${#SECURITY_FIXES[@]} -gt 0 ]; then
BUMP_TYPE="patch" # Security fixes → patch
elif [ ${#FIXES[@]} -gt 0 ]; then
BUMP_TYPE="patch" # Bug fixes → patch
else
BUMP_TYPE="patch" # Default
fi
}Результат:
ℹ️ Commit summary:
🔥 1 breaking changes
🔒 2 security fixes
✨ 5 features
🐛 3 bug fixes
⚠️ 1 deprecations
♻️ 2 refactors
✅ Auto-detected version bump: major (Found 1 breaking change(s))Фича 4: Генерация CHANGELOG по Keep a Changelog
Проблема: Писать changelog вручную — это:
Скучно
Легко что-то забыть
Нет единого формата
Решение: Скрипт генерирует changelog по стандарту Keep a Changelog:
generate_changelog_entry() {
local version="$1"
local date="$2"
echo "## [$version] - $date"
echo ""
# Security section FIRST (highest priority!)
if [ ${#SECURITY_FIXES[@]} -gt 0 ]; then
echo "### Security"
for commit in "${SECURITY_FIXES[@]}"; do
format_changelog_line "$commit"
done
fi
# Added section (features)
if [ ${#FEATURES[@]} -gt 0 ]; then
echo "### Added"
for commit in "${FEATURES[@]}"; do
format_changelog_line "$commit"
done
fi
# Changed section (breaking, refactor, perf)
if [ ${#BREAKING_CHANGES[@]} -gt 0 ]; then
echo "### Changed"
for commit in "${BREAKING_CHANGES[@]}"; do
format_changelog_line "$commit" "⚠️ BREAKING: "
done
fi
# ... Deprecated, Removed, Fixed, Other
}Форматирование строк:
format_changelog_line() {
local commit="$1"
local hash=$(echo "$commit" | awk '{print $1}')
local message=$(echo "$commit" | cut -d' ' -f2-)
# Extract scope: "feat(api): add auth" → "**api**: add auth"
local scope_pattern='^[a-z]+(\(([^)]+)\))?!?:[ ]+(.+)$'
if [[ "$message" =~ $scope_pattern ]]; then
local scope="${BASH_REMATCH[2]}"
local msg="${BASH_REMATCH[3]}"
if [ -n "$scope" ]; then
echo "- **${scope}**: ${msg} (${hash})"
else
echo "- ${msg} (${hash})"
fi
fi
}Результат в CHANGELOG.md:
## [2.0.0] - 2025-12-20
### Security
- **auth**: fix JWT token validation bypass (abc1234)
### Added
- **agents**: add security-scanner worker (def5678)
- **commands**: add /health-security command (ghi9012)
### Changed
- ⚠️ BREAKING: **api**: change authentication flow (jkl3456)
### Fixed
- **build**: fix TypeScript compilation errors (mno7890)Фича 5: Безопасный Rollback
Проблема: Что если скрипт упадёт посередине? Вы получите:
Половину обновлённых package.json
Незаконченный changelog
Созданный тег без пуша
💀
Решение: Скрипт создаёт бэкапы ПЕРЕД изменением файлов и восстанавливает их при ошибке:
# State tracking for rollback
CREATED_COMMIT=""
CREATED_TAG=""
declare -a BACKUP_FILES=()
create_backup() {
local file="$1"
if [ ! -f "$file" ]; then return 0; fi
local backup="${file}.backup.$$"
cp "$file" "$backup"
BACKUP_FILES+=("$backup")
log_info "Created backup: ${backup##*/}"
}
# Trap for cleanup on error
trap cleanup EXIT
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log_error "Error occurred during release process"
log_warning "Rolling back changes..."
# Delete tag if created
if [ -n "$CREATED_TAG" ]; then
git tag -d "$CREATED_TAG" 2>/dev/null || true
log_success "Deleted tag $CREATED_TAG"
fi
# Rollback commit
if [ -n "$CREATED_COMMIT" ]; then
git reset --soft HEAD~1 2>/dev/null || true
log_success "Rolled back commit"
fi
# Restore files from backups
restore_from_backups
log_info "Rollback complete. Files restored from backups."
else
cleanup_backups # Success - remove backup files
fi
}Почему не git restore?
Мы специально НЕ используем git restore, потому что это затёрло бы ручные правки, которые пользователь мог сделать до запуска скрипта. Бэкапы сохраняют именно то состояние, которое было до release.sh.
Фича 6: SIGPIPE-safe для macOS
Проблема: На macOS с bash 3.2 и set -o pipefail, команда head вызывает SIGPIPE (exit code 141), когда закрывает pipe раньше, чем пишущий процесс завершится.
# Это падает на macOS:
git tag --sort=-version:refname | head -n 1Решение: Заменяем head на awk:
# SIGPIPE-safe replacement for head -n N
safe_head() {
local n="${1:-1}"
awk -v n="$n" 'NR <= n {print} NR > n {exit}'
}
# Optimized for first line only
safe_first() {
awk 'NR==1 {print; exit}'
}
# Usage:
LAST_TAG=$(git tag --sort=-version:refname | safe_first || echo "")Теперь работает на Linux, macOS, и даже на древних системах.
Фича 7: Проверка Remote Status
Проблема: Вы делаете релиз, а remote уже ушёл вперёд. Push фейлится, вы в раздрае.
Решение: Pre-flight check перед началом:
check_remote_status() {
local branch="$1"
# Fetch latest (without merging)
git fetch origin "$branch" --quiet 2>/dev/null
# Check if remote is ahead
local behind
behind=$(git rev-list --count "HEAD..origin/$branch" 2>/dev/null || echo "0")
if [ "$behind" -gt 0 ]; then
log_error "Remote is $behind commit(s) ahead of local"
log_info "Please pull changes first:"
echo " git pull origin $branch"
exit 1
fi
log_success "Local branch is up to date with remote"
}Интеграция с Claude Code
Магия /push в том, что это не просто bash-скрипт. Это slash-команда, которую Claude Code понимает нативно.
Как создать свою slash-команду
Создайте файл
.claude/commands/my-command.md:
---
description: My awesome automation
argument-hint: [arg1|arg2]
---
Here's what this command does...
# Usage:
bash .claude/scripts/my-script.sh $ARGUMENTS --yesИспользуйте в Claude Code:
/my-command arg1Claude Code подставит arg1 вместо $ARGUMENTS и выполнит скрипт.
Флаг --yes
Обратите внимание на --yes в команде. Это критически важно для интеграции с Claude Code:
# Skip confirmation if --yes flag provided
if [ "$auto_confirm" = "true" ]; then
log_info "Auto-confirming release (--yes flag provided)"
return 0
fi
read -p "Proceed with release? [Y/n]: " confirmБез --yes скрипт будет ждать пользовательского ввода, а Claude Code не умеет интерактивно отвечать на read.
Preview Mode
Перед выполнением релиза скрипт показывает превью всех изменений:
show_preview() {
cat << EOF
═══════════════════════════════════════════════════════════
RELEASE PREVIEW
═══════════════════════════════════════════════════════════
📌 Version: $CURRENT_VERSION → $NEW_VERSION (${BUMP_TYPE^^})
Reason: $AUTO_DETECT_REASON
📊 Commits included: ${#ALL_COMMITS[@]}
🔥 ${#BREAKING_CHANGES[@]} breaking changes
🔒 ${#SECURITY_FIXES[@]} security fixes
✨ ${#FEATURES[@]} features
🐛 ${#FIXES[@]} bug fixes
📦 Package Updates:
$(find "$PROJECT_ROOT" -name "package.json" -not -path "*/node_modules/*" | while read pkg; do
echo " ✓ ${pkg#$PROJECT_ROOT/}"
done)
📄 CHANGELOG.md Entry:
───────────────────────────────────────────────────────────
$(generate_changelog_entry "$NEW_VERSION" "$DATE")
───────────────────────────────────────────────────────────
💬 Git Commit Message:
───────────────────────────────────────────────────────────
chore(release): v$NEW_VERSION
───────────────────────────────────────────────────────────
🏷️ Git Tag: v$NEW_VERSION
🌿 Branch: $BRANCH
EOF
}Вы видите ВСЁ, что произойдёт, до того как это произойдёт.
Реальные примеры использования
Быстрый патч-релиз
/push patch
# 1.3.4 → 1.3.5Minor релиз с новыми фичами
/push minor
# 1.3.5 → 1.4.0Major релиз (breaking changes)
/push major
# 1.4.0 → 2.0.0Авто-определение по коммитам
/push
# Скрипт сам решит: есть feat: → minor, только fix: → patchСтатистика: До и После
Метрика | До (вручную) | После (/push) |
|---|---|---|
Время на релиз | 5-10 минут | 3-5 секунд |
Ошибки в changelog | ~30% релизов | 0% |
Забытые package.json | ~20% | 0% |
Рассинхрон версий | Постоянно | Никогда |
Настроение разработчика | 😤 | 😎 |
Где взять
Скрипт — часть open-source проекта Claude Code Orchestrator Kit:
Репозиторий: github.com/maslennikov-ig/claude-code-orchestrator-kit
Лицензия: MIT (бесплатно, можно использовать в коммерческих проектах)
Что ещё есть в репозитории:
36+ AI-агентов для Claude Code
20+ slash-команд
18+ переиспользуемых скиллов
7 MCP-конфигураций
Quality gates для CI/CD
Быстрый старт
# Клонируем
git clone https://github.com/maslennikov-ig/claude-code-orchestrator-kit.git
# Копируем .claude в свой проект
cp -r claude-code-orchestrator-kit/.claude /path/to/your/project/
# Используем!
/push patchЗаключение
/push — это пример того, как можно автоматизировать рутинные задачи в Claude Code. Мы потратили день на написание 1000+ строк bash, но теперь экономим 5-10 минут на каждом релизе. При 2-3 релизах в день это час в неделю.
А главное — больше никаких "чёрт, я забыл добавить фикс в changelog" и "почему у меня версия 1.3.2, а на remote 1.3.5?".
P.S. Если у вас есть идеи, как улучшить скрипт — открывайте issue или PR на GitHub. Проект развивается, и мы рады контрибьюторам.
Автор: Игорь Масленников
Пишу про AI-агентов, LLM-бенчмарки и архитектуру софта.
📢 Мой канал в Telegram: @maslennikovigor — там я публикую свежие бенчмарки и DevOps-лайфхаки.
💬 Личный контакт: @maslennikovig
А как вы автоматизируете релизы? Делитесь в комментариях — интересно узнать, какие инструменты используете.