---
name: "local-publish"
description: "Bump version, commit, build, publish to PyPI, and push to remote"
short_desc: "Version bump → commit → PyPI publish → git push"
version: "1.0.0"
tags: ["deploy", "pypi", "publish", "version"]
---

# Local Publish Skill

End-to-end local release workflow: version bump, commit/squash, PyPI upload, git push.

## When to Use

- Ready to publish a new version of the package to PyPI
- After completing a set of changes that should be released together
- When the user says "publish", "release", "deploy", or "배포"

## Prerequisites

- `pyproject.toml` and `src/agrules/__init__.py` contain matching version strings
- `twine` is installed and configured with PyPI credentials
- Working tree should be clean or have only the changes to be released

## Workflow

### Phase 1: Pre-flight Check

1. **Working tree status**: `git status --porcelain`
   - Note uncommitted changes — these will be included in the release commit
2. **Current version**: read from `pyproject.toml`
3. **Recent commits since last version bump**:

```bash
git log --oneline $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~10)..HEAD
```

4. **Determine bump type** — ask the user:
   - `patch` (0.2.4 → 0.2.5): bug fixes, small additions
   - `minor` (0.2.4 → 0.3.0): new features, non-breaking changes
   - `major` (0.2.4 → 1.0.0): breaking changes

### Phase 2: Version Bump

```bash
make bump-patch   # or bump-minor / bump-major
```

Verify both files were updated:

```bash
grep 'version' pyproject.toml | head -1
grep '__version__' src/agrules/__init__.py
```

### Phase 3: Sync Data

```bash
make sync-data
```

Ensures `src/agrules/data/` matches `content/`.

### Phase 4: Commit

Stage all relevant changes and create a release commit:

```bash
git add -A
git commit -m "chore: bump version to <new-version>"
```

**Squash option**: if the user requests squashing, squash recent commits into the release commit:

```bash
# Count commits to squash (user specifies or auto-detect since last version bump)
git rebase -r HEAD~<N>
```

Only squash if explicitly requested — default is a new commit on top.

### Phase 5: Build & Publish

```bash
make clean build
python3 -m twine upload dist/*
```

If twine fails:
- Check credentials: `~/.pypirc` or environment variables `TWINE_USERNAME` / `TWINE_PASSWORD`
- Check if version already exists on PyPI (409 Conflict)
- **Do NOT retry with a different version** — fix the issue first

### Phase 6: Push & Verify

```bash
git push origin <current-branch>
```

If push fails (e.g., remote has new commits):
- `git pull --rebase origin <branch>` then retry push
- **Never force-push** without explicit user confirmation

Verify:

```bash
# Confirm pushed
git log --oneline -3
git status

# Confirm on PyPI (optional)
pip index versions agrules 2>/dev/null || echo "Check https://pypi.org/project/agrules/"
```

Report to user:
- New version number
- PyPI package URL
- Git commit hash and branch

## Safety Rules

- **Never force-push** without explicit user confirmation
- **Never skip pre-commit hooks** (`--no-verify`)
- **Always verify version bump** in both `pyproject.toml` and `__init__.py` before committing
- If PyPI upload fails, **do NOT bump version again** — fix the upload issue
- If any phase fails, **stop and report** — do not continue to the next phase
