# Release Plan — Unfamiliar

---

## Stage 0: Repository architecture — private dev + public release (Do now)

### The problem

All development work lives in a private repo, including `_planning/`, `.claude/`, and other files that are valuable internally but should never be public. The goal is to keep everything in one place while publishing a clean subset to the public repo.

### The solution: two remotes + `export-ignore`

Git has a built-in mechanism for this. Files and directories marked with `export-ignore` in `.gitattributes` are automatically excluded when you run `git archive`. A small script uses `git archive` to create a clean snapshot and pushes it to a second remote (the public repo).

Your local clone has two remotes:
- `origin` → private GitHub repo (all commits go here as normal)
- `public` → public GitHub repo (only gets clean release snapshots)

The public repo will have **one commit per release**, tagged with the version number. This is clean and professional — users see a CHANGELOG for what changed between versions. Your granular development history stays in the private repo.

**Step 7: Test the script before your first real release**

Run the script without a tag to verify it works:
```bash
scripts/push-public.sh HEAD
```

Then check your public repo on GitHub — you should see a single commit with the expected files and none of the private ones. Verify that `_planning/`, `.claude/`, and `scripts/` are absent. If something is wrong, the public repo only has one test commit; just run the script again after fixing.

### How daily development changes (it doesn't)

Your normal workflow is completely unchanged:
```bash
git add . && git commit -m "..."
git push origin main       # private repo, as always
```

The push-public script only runs at release time. See the updated release checklist in Stage 4.

### What the public repo history looks like

```
v0.2.0  Release v0.2.0    ← clean, no private files
v0.1.1  Release v0.1.1
v0.1.0  Release v0.1.0
```

One commit per release, each tagged. Clean and professional. The interesting "what changed" story is told by the CHANGELOG, not the commit log.

---
### 3.3 Set up PyPI Trusted Publisher

Do this once, before your first release tag:

- [x] Create an account at https://pypi.org
- [x] Create an account at https://test.pypi.org (for testing)
- [x] On PyPI, go to your account → Publishing → "Add a new pending publisher"
  - PyPI project name: `unfamiliar` (or your chosen name)
  - GitHub owner: your GitHub username
  - Repository: `unfamiliar`
  - Workflow filename: `publish.yml`
  - Environment: `pypi` (if you added the environment gate above)
- [x] Do the same on TestPyPI first for a dry run

### 3.4 Test publish on TestPyPI before going live

Modify the publish step to point at TestPyPI first:
```yaml
      - name: Publish to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: https://test.pypi.org/legacy/
```

Tag a test release (e.g., `v0.1.0-rc1`), verify the workflow succeeds and the package appears on TestPyPI, then switch back to the production PyPI action for your real release.

---


## Stage 5: Your first release

### 5.1 Versioning strategy

Use **Semantic Versioning**: `MAJOR.MINOR.PATCH`

- **PATCH** (0.1.1): Bug fixes, no new features
- **MINOR** (0.2.0): New features, backwards compatible
- **MAJOR** (1.0.0): Stable, complete, intentionally committing to the public API

**Start at `0.1.0`, not `1.0.0`.** In open source convention, `0.x` signals "this works but the API/behavior may change." It sets honest expectations for early adopters and gives you room to iterate without the pressure of SemVer's stability guarantee. The progression here looks like:

| Version | Milestone |
|---|---|
| `0.1.0` | MVP — Phase 20 complete, all deferred items resolved, published to PyPI |
| `0.2.0` | v1.1 features (mode/character selection, annotation, staleness detection) |
| `0.3.0` | Additional themes, quality-of-life improvements |
| `1.0.0` | Feature-stable, polished, documented, test coverage strong, no known critical bugs |

### 5.2 The release checklist (do this every release)

**In the private repo:**
- [ ] All planned features for this version are merged to `main`
- [ ] All tests pass on CI
- [ ] Update `src/unfamiliar/__init__.py` version number (e.g., `__version__ = "0.1.0"`)
- [ ] Update `CHANGELOG.md` — move items from `[Unreleased]` to `[0.1.0] - YYYY-MM-DD`
- [ ] Commit: `git commit -m "Release v0.1.0"`
- [ ] Tag: `git tag v0.1.0`
- [ ] Push to private: `git push origin main && git push origin v0.1.0`

**Push to public repo (triggers PyPI publish):**
- [ ] Run: `scripts/push-public.sh v0.1.0`
  - This creates a clean snapshot (private files excluded), commits it, tags it, and force-pushes to the public remote
  - The tag push triggers `.github/workflows/publish.yml` on the public repo, which publishes to PyPI
- [ ] Verify the package appears on PyPI (takes a few minutes)

**Create the GitHub Release:**
- [ ] Create a GitHub Release (see below)

### 5.3 GitHub Releases

GitHub Releases are separate from tags. They're the human-friendly announcement of a new version.

- Go to your repo → Releases → "Draft a new release"
- Select your tag (`v0.1.0`)
- Title: `v0.1.0 — Initial Release`
- Body: paste the relevant section from your CHANGELOG
- For pre-1.0 releases, check "Pre-release" if you want to signal it's early-stage

This creates a page that users and tooling (Dependabot, release watchers) can follow.

---

## Stage 6: Ongoing release cadence

### 6.1 How to think about when to release

As a solo dev, you don't need a fixed schedule. A few practical guidelines:

- **Release when a coherent set of features is done**, not when a single feature is done and not on an arbitrary calendar
- **Never release on a Friday** — if something breaks, you don't want to be debugging over the weekend
- **Ship fixes quickly** — a patch release (0.1.1) for a real bug is worth doing within days
- **Batch features** — new features can accumulate in `[Unreleased]` for weeks and ship together

### 6.2 The development loop

```
main branch (always releasable)
  └── feature/branch-name  (your work)
       └── merge to main when done
            └── tag + release when enough is accumulated
```

For a solo dev, you don't need a `develop` branch or complex git flow. Work on feature branches, merge to `main`, release when ready.

### 6.3 Maintaining CHANGELOG discipline

Every meaningful commit that changes behavior for users should have a CHANGELOG entry. Add it in the same commit as the code change, not later. Categories:

- **Added** — new features
- **Changed** — behavior changes to existing features
- **Deprecated** — features being phased out
- **Removed** — features that are gone
- **Fixed** — bug fixes
- **Security** — security patches

### 6.4 The path to v1.0

`1.0.0` is a signal, not a feature set. It means: "I'm committed to this API. If I break something, I'll bump the major version." For a tool like Unfamiliar, `1.0` makes sense when:

- No known critical bugs
- Configuration and CLI interface are stable (you won't rename commands or change env var names)
- Documentation is complete (README, CHANGELOG, any user-facing guides)
- Test coverage is strong
- v1.1 features are shipped or explicitly scoped to `1.x`

There's no rush. Staying on `0.x` is completely acceptable and honest.

