# Contributing

Thanks for your interest in `@ironarachne/word-generator`. This document covers
how to get set up, how changes get merged, and how releases are cut.

## Getting set up

```bash
git clone https://github.com/ironarachne/word-generator.git
cd word-generator
npm install
```

Node 20 or newer is required. CI tests against Node 20, 22, and 24.

## Everyday commands

| Command            | What it does                                     |
| ------------------ | ------------------------------------------------ |
| `npm run check`    | Lint, build, and test — run this before pushing.  |
| `npm test`         | Run the Vitest suite.                             |
| `npm run lint`     | Check formatting and lint rules with Biome.       |
| `npm run lint:fix` | Apply formatting and safe lint fixes.             |
| `npm run build`    | Compile TypeScript to `dist/`.                    |
| `npm run docs`     | Generate the TypeDoc site into `docs/`.           |

`dist/` and `docs/` are build output. They are gitignored and generated by CI —
never commit them.

## How changes get merged

`main` is protected. Nothing is pushed to it directly; every change lands
through a pull request.

1. **Open an issue first** for anything beyond a small fix, so the approach can
   be agreed before you spend time on it.
2. **Branch from `main`.** Name it for the work: `fix/unmatched-paren-message`,
   `feat/weighted-elements`, `chore/bump-biome`.
3. **Write the change and its tests.** Follow [CODE_STYLE.md](CODE_STYLE.md) —
   in particular the rules on TSDoc, error messages, and determinism.
4. **Run `npm run check`.** CI runs the same thing; catching it locally is
   faster.
5. **Open a pull request** against `main` and fill in the template. Keep pull
   requests focused — one concern each. Unrelated cleanups belong in their own
   pull request.
6. **CI must pass.** The `Lint` and `Test` jobs are required checks; the merge
   button stays disabled until they are green.
7. **Get a review.** Address feedback with new commits rather than
   force-pushing, so reviewers can see what changed.
8. **Squash and merge.** The pull request title becomes the commit subject on
   `main`, so write it as one. Your branch is deleted automatically.

## Commit messages

Use [Conventional Commits](https://www.conventionalcommits.org/) for pull
request titles and commits. This keeps generated release notes readable.

```
feat: support weighted choices inside groups
fix: reject a repeat operator at the end of a pattern
docs: document the clicks symbol in the README table
chore: bump biome to 2.4
```

Mark a breaking change with a `!` — `feat!: add th to the fricatives set` — and
explain the impact in the pull request body.

## What counts as breaking

This library's contract is that a given pattern and seed always produce the
same word. Any change that alters that is breaking, even if no signature
changes:

- Adding, removing, or reordering elements in an existing set.
- Reassigning a symbol to a different set.
- Changing how many times the generator draws from the RNG, or in what order.
- Changing how the tokenizer splits a pattern.
- Bumping the `@ironarachne/rng` major version, which changes the sequence
  behind every seed.

Such a change needs a major version bump and a clear note in the pull request.

## Reporting security issues

This library generates fictional words and is not intended for use where
randomness is a security boundary — the underlying RNG is explicitly not
cryptographically secure, which is documented, not a vulnerability. For
anything else that looks security-relevant, email ben@overmyer.net rather than
opening a public issue.

## Releasing

Releases are automated. Maintainers cut one by tagging `main`.

If you use Claude Code, `/release <patch|minor|major>` drives the whole
sequence below and verifies the result; `.claude/skills/release/SKILL.md` is
the source of truth for it. The manual steps are:

1. Land everything intended for the release on `main`.
2. Bump the version on a branch. `--no-git-tag-version` matters: `main` is
   protected, so the bump has to merge before the tag can point at it.

   ```bash
   git checkout -b release/2.5.0
   npm version 2.5.0 --no-git-tag-version
   git commit -aq -m "chore: release 2.5.0"
   git push -u origin release/2.5.0
   ```

3. Open a pull request for it and merge once CI is green.
4. Tag the merged commit on `main` and push the tag:

   ```bash
   git checkout main && git pull
   git tag -a v2.5.0 -m "Release 2.5.0"
   git push origin v2.5.0
   ```

Pushing a `v*` tag triggers `.github/workflows/release.yml`, which:

1. Verifies the tag matches `package.json`'s version, then lints, builds, and
   tests. A mismatch fails the release before anything is published.
2. Publishes to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements)
   attached.
3. Creates a GitHub Release with auto-generated notes from the merged pull
   requests.

Separately, every push to `main` rebuilds the TypeDoc site and deploys it to
[ironarachne.github.io/word-generator](https://ironarachne.github.io/word-generator).

### One-time maintainer setup

The release workflow needs these configured before the first tagged release:

- **npm trusted publishing** — on npmjs.com, under the
  `@ironarachne/word-generator` package settings, add a trusted publisher with:

  | Field            | Value              |
  | ---------------- | ------------------ |
  | Organization     | `ironarachne`      |
  | Repository       | `word-generator`   |
  | Workflow         | `release.yml`      |
  | Environment      | `release`          |

  The workflow filename is the basename only — not a path. The environment
  must read `release` to match the `publish` job, or be left blank.

  This replaces a long-lived `NPM_TOKEN`: the workflow authenticates over
  OIDC, so there is no publish secret stored in the repository at all.

- **Pages** — Settings → Pages → Source set to **GitHub Actions**.
- The `release` environment, which you can use to require manual approval
  before a publish goes out.
