@verndale/ai-commit 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +221 -52
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,10 +2,16 @@
2
2
 
3
3
  AI-assisted [Conventional Commits](https://www.conventionalcommits.org/) with **bundled [commitlint](https://commitlint.js.org/)** so generated messages match the same rules enforced in hooks.
4
4
 
5
+ ---
6
+
5
7
  ## Requirements
6
8
 
7
- - **Node.js** `>=24.14.0`
8
- - This repo uses **pnpm** (`packageManager` is pinned in `package.json`; enable via [Corepack](https://nodejs.org/api/corepack.html): `corepack enable`).
9
+ | | |
10
+ | --- | --- |
11
+ | **Node.js** | `>=24.14.0` |
12
+ | **Package manager** | This repo pins **pnpm** in `package.json`. Enable with [Corepack](https://nodejs.org/api/corepack.html): `corepack enable`. |
13
+
14
+ ---
9
15
 
10
16
  ## Install
11
17
 
@@ -13,48 +19,118 @@ AI-assisted [Conventional Commits](https://www.conventionalcommits.org/) with **
13
19
  pnpm add -D @verndale/ai-commit
14
20
  ```
15
21
 
16
- The same package works with **npm** or **yarn** (for example `npm install -D @verndale/ai-commit`); use your package manager’s `exec` / `npx` equivalent where the docs show `pnpm exec`.
22
+ **npm** and **yarn** work too (`npm install -D @verndale/ai-commit`). Where this doc says `pnpm exec`, use your tool’s equivalent (`npx`, `yarn exec`, etc.).
23
+
24
+ ---
25
+
26
+ ## Setup
27
+
28
+ Do these **in order** from your **git repository root** (the directory that contains `package.json`).
29
+
30
+ ### 1. Install the package
31
+
32
+ See [Install](#install).
33
+
34
+ ### 2. Run init
35
+
36
+ ```bash
37
+ pnpm exec ai-commit init
38
+ ```
39
+
40
+ **What init does (by default):**
41
+
42
+ | Action | Detail |
43
+ | --- | --- |
44
+ | Env files | Merges **`.env`** and **`.env-example`**; creates **`.env-example`** from the bundled template if missing. Template reference: [`.env-example`](.env-example). |
45
+ | Husky | Runs **`npx husky@9 init`** if Husky is not present. |
46
+ | `package.json` | Adds missing **`commit`**, **`prepare`**, **`husky`** entries when the file exists. |
47
+ | Hooks | Writes **`.husky`** hook files. |
48
+
49
+ If **`package.json`** changed, run **`pnpm install`** (or `npm install`) again.
50
+
51
+ ### 3. Add your API key
52
+
53
+ Set **`OPENAI_API_KEY`** in **`.env`** and/or **`.env.local`**. Duplicate keys: **`.env.local`** wins.
54
+
55
+ ---
56
+
57
+ ### Init: flags and shortcuts
58
+
59
+ | Flag | Use when |
60
+ | --- | --- |
61
+ | *(none)* | Full setup: env files + Husky + hooks + `package.json` updates (when applicable). |
62
+ | `--env-only` | You only want env / **`.env-example`** updates—no Git hooks. |
63
+ | `--husky` | Hooks + Husky only; skips **`package.json`** changes. Combine with **`--workspace`** if you need **`package.json`** merged again. |
64
+ | `--force` | Replace **`.env`** and **`.env-example`** with the bundled template **(destructive)** and/or overwrite existing Husky hook files. |
65
+
66
+ **Edge cases**
67
+
68
+ | Situation | Behavior |
69
+ | --- | --- |
70
+ | Not in a git repo | Init updates env files only and reports that Git/Husky were skipped. |
71
+ | Template filename | The published file is **`.env-example`** (hyphen), not **`.env.example`**. |
72
+ | Without **`--force`** | Missing **`.env-example`** is created; otherwise missing ai-commit keys are **appended** to **`.env`** (and the example file) without wiping the file. |
73
+
74
+ ---
75
+
76
+ ### Setup — command cheat sheet
77
+
78
+ ```bash
79
+ pnpm add -D @verndale/ai-commit
80
+ pnpm exec ai-commit init
81
+ # Set OPENAI_API_KEY in .env or .env.local
82
+ ```
83
+
84
+ Optional variants:
17
85
 
18
- ## Quick setup (deterministic order)
86
+ ```bash
87
+ pnpm exec ai-commit init --env-only
88
+ pnpm exec ai-commit init --husky
89
+ pnpm exec ai-commit init --force
90
+ ```
91
+
92
+ ---
19
93
 
20
- 1. **Install** the dev dependency (see [Install](#install)).
21
- 2. **Init** — From the **git repo root** (where **`package.json`** lives), run **`pnpm exec ai-commit init`**. That merges **`.env`** and **`.env-example`** (creates **`.env-example`** from the bundled template if it is missing; see [`.env-example`](.env-example) for keys and comments), runs **`npx husky@9 init`** if Husky is not present, adds missing **`commit`** / **`prepare`** / **`husky`** entries to **`package.json`** when the file exists, and writes **`.husky`** hooks. **Install dependencies** afterward if **`package.json`** changed (`pnpm install`, `npm install`, etc.).
22
- - Not in a git repo? **init** only updates env files and explains that Git/Husky were skipped.
23
- - Env files only? Use **`pnpm exec ai-commit init --env-only`**.
24
- - Hooks only (no **`package.json`** changes)? Use **`pnpm exec ai-commit init --husky`**.
25
- 3. **Secrets** — Set **`OPENAI_API_KEY`** in `.env` and/or `.env.local` (`.env.local` overrides `.env` for duplicate keys).
94
+ ## Environment variables
26
95
 
27
- Use **`ai-commit init --force`** to replace **`.env`** and **`.env-example`** with the bundled template (destructive) or to overwrite existing Husky hook files. Without **`--force`**, **init** creates **`.env-example`** when missing and otherwise appends missing ai-commit keys (same as **`.env`**). The published template file is **`.env-example`** (hyphen, not **`.env.example`**).
96
+ | Variable | Notes |
97
+ | --- | --- |
98
+ | **`OPENAI_API_KEY`** | Required for **`ai-commit run`** and for AI in **`prepare-commit-msg`** when you want the model. |
99
+ | **`COMMIT_AI_MODEL`** | Optional; default **`gpt-4o-mini`**. |
100
+ | **Load order** | CLI loads **`.env`**, then **`.env.local`** (same key → `.env.local` wins). |
101
+
102
+ **Comments:** If another tool already documents **`OPENAI_API_KEY`** or **`COMMIT_AI_MODEL`**, **`ai-commit init`** inserts a `# @verndale/ai-commit — …` line above the assignment when that line is missing. It does not remove existing comments.
28
103
 
29
- ## Environment
104
+ **Other tooling (optional):** `PR_*` for [`@verndale/ai-pr`](https://www.npmjs.com/package/@verndale/ai-pr) and **`pnpm run pr:create`** / PR workflows; `RELEASE_NOTES_AI_*` for [`tools/semantic-release-notes.cjs`](./tools/semantic-release-notes.cjs). Use a GitHub PAT as **`GH_TOKEN`** or **`GITHUB_TOKEN`** when calling the GitHub API outside Actions.
30
105
 
31
- - **`OPENAI_API_KEY`** — Required for `ai-commit run` (and for AI-filled `prepare-commit-msg` when you want the model). Optional `COMMIT_AI_MODEL` (default `gpt-4o-mini`).
32
- - **Shared env vars** — If another tool already documents **`OPENAI_API_KEY`** or **`COMMIT_AI_MODEL`**, **`ai-commit init`** adds its own `# @verndale/ai-commit — …` line immediately above the assignment when missing; it does not remove or replace existing comment lines.
33
- - The CLI loads **`.env`** then **`.env.local`** from the current working directory (project root); values in `.env.local` override `.env` for the same key.
34
- - **Optional tooling:** `PR_*` env vars for [`@verndale/ai-pr`](https://www.npmjs.com/package/@verndale/ai-pr) (`pnpm run pr:create` in this repo) / the **Create or update PR** workflow; `RELEASE_NOTES_AI_*` for [`tools/semantic-release-notes.cjs`](./tools/semantic-release-notes.cjs). Use a GitHub PAT as **`GH_TOKEN`** (or `GITHUB_TOKEN`) when calling the GitHub API outside Actions.
106
+ ---
35
107
 
36
108
  ## Commit policy (v2)
37
109
 
38
- - **Mandatory scope** — Every header is `type(scope): Subject` (or `type(scope)!:` when breaking). The **scope is not chosen by the model**; it is derived from staged paths (see [`lib/core/message-policy.js`](lib/core/message-policy.js)) and falls back to a short name from `package.json` (e.g. `ai-commit`).
110
+ - **Mandatory scope** — Headers are `type(scope): Subject` (or `type(scope)!:` when breaking). Scope comes from staged paths ([`lib/core/message-policy.js`](lib/core/message-policy.js)), not from the model, with fallback from `package.json` (e.g. `ai-commit`).
39
111
  - **Types** — `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`.
40
112
  - **Subject** — Imperative, Beams-style (first word capitalized), max **50** characters, no trailing period.
41
- - **Body / footer** — Wrap lines at **72** characters when present.
113
+ - **Body / footer** — Wrap at **72** characters when present.
42
114
  - **Issues** — If branch or diff mentions `#123`, footers may add `Refs #n` / `Closes #n` (no invented numbers).
43
- - **Breaking changes** — Only when policy detects governance-related files (commitlint, Husky, this package’s rules/preset); otherwise `!` and `BREAKING CHANGE:` lines are stripped.
44
- - **Staged diff for AI** — Lockfile and common binary globs are **excluded** from the diff text sent to the model (see [`lib/core/git.js`](lib/core/git.js)); path detection still uses the full staged file list.
115
+ - **Breaking changes** — Only when policy detects governance-related files (commitlint, Husky, this package’s rules/preset); otherwise `!` and `BREAKING CHANGE:` are stripped.
116
+ - **Staged diff for AI** — Lockfile and common binary globs are excluded from the text sent to the model ([`lib/core/git.js`](lib/core/git.js)); path detection still uses the full staged file list.
117
+
118
+ **Semver:** v2 tightens commitlint (mandatory scope, stricter lengths). If you `extends` this preset, review [`lib/rules.js`](lib/rules.js) and adjust overrides as needed.
45
119
 
46
- **Semver:** v2 tightens commitlint (mandatory scope, stricter lengths). If you `extends` this preset, review [lib/rules.js](lib/rules.js) and adjust overrides as needed.
120
+ ---
47
121
 
48
- ## Commands
122
+ ## CLI reference
49
123
 
50
124
  | Command | Purpose |
51
125
  | --- | --- |
52
- | `ai-commit run` | Generate a message from the staged diff and run `git commit`. |
53
- | `ai-commit init [--force] [--env-only] [--husky] [--workspace]` | Merge env keys (including **`.env-example`**), then **`npx husky@9 init`** if needed, merge **`package.json`** when present, write hooks. **`--env-only`** stops after env files. **`--husky`** skips `package.json` (hooks + Husky only); use **`--husky --workspace`** to include **`package.json`** again. **`--force`** replaces `.env` / `.env-example` / overwrites hooks. |
54
- | `ai-commit prepare-commit-msg <file> [source]` | Git `prepare-commit-msg` hook: fill an empty message; skips `merge` / `squash`. |
55
- | `ai-commit lint --edit <file>` | Git `commit-msg` hook: run commitlint with this package’s default config. |
126
+ | **`ai-commit run`** | Build a message from the staged diff and run **`git commit`**. |
127
+ | **`ai-commit init`** | Env merge (including **`.env-example`**), Husky if needed, **`package.json`** when present, hooks. See [flags](#init-flags-and-shortcuts). |
128
+ | **`ai-commit prepare-commit-msg <file> [source]`** | Hook: fill an empty message; skips `merge` / `squash`. |
129
+ | **`ai-commit lint --edit <file>`** | Hook: commitlint with this package’s default config. |
56
130
 
57
- ## package.json scripts (example)
131
+ ---
132
+
133
+ ## `package.json` script (example)
58
134
 
59
135
  ```json
60
136
  {
@@ -64,9 +140,11 @@ Use **`ai-commit init --force`** to replace **`.env`** and **`.env-example`** wi
64
140
  }
65
141
  ```
66
142
 
143
+ ---
144
+
67
145
  ## Husky (manual setup)
68
146
 
69
- **`pnpm exec ai-commit init`** does this automatically. To add hooks by hand, install Husky (`husky` + `"prepare": "husky"` in `package.json` if needed), then add the snippets below.
147
+ **`pnpm exec ai-commit init`** configures Husky for you. To add hooks by hand, install Husky (`husky` + `"prepare": "husky"` in `package.json` if needed), then add:
70
148
 
71
149
  **`.husky/prepare-commit-msg`**
72
150
 
@@ -86,15 +164,17 @@ pnpm exec ai-commit prepare-commit-msg "$1" "$2"
86
164
  pnpm exec ai-commit lint --edit "$1"
87
165
  ```
88
166
 
89
- Hooks created by **`init`** use **`pnpm exec ai-commit`** when **`pnpm-lock.yaml`** exists, otherwise **`npx --no ai-commit`**. Edit the hook files if you use another runner.
167
+ Hooks from **`init`** use **`pnpm exec ai-commit`** when **`pnpm-lock.yaml`** exists; otherwise **`npx --no ai-commit`**. Edit the files if you use another runner.
168
+
169
+ **Already using Husky?** If **`.husky/_/husky.sh`** exists, **`init`** does not run **`npx husky@9 init`**. **`package.json`** is only amended for missing **`commit`**, **`prepare`**, or **`devDependencies.husky`**. Existing **`.husky/prepare-commit-msg`** and **`.husky/commit-msg`** are not overwritten unless you use **`ai-commit init --force`**.
90
170
 
91
- **If Husky is already set up:** **`init`** does not run **`npx husky@9 init`** when **`.husky/_/husky.sh`** already exists. **`package.json`** is only updated for missing **`commit`**, **`prepare`**, or **`devDependencies.husky`** (nothing is replaced). Hook files **`.husky/prepare-commit-msg`** and **`.husky/commit-msg`** are left alone if they already exist; use **`ai-commit init --force`** to overwrite them with the ai-commit snippets.
171
+ ---
92
172
 
93
173
  ## commitlint without a second install
94
174
 
95
- Use the packaged binary from hooks (`ai-commit lint --edit`) as above.
175
+ Use **`ai-commit lint --edit`** from hooks (see above).
96
176
 
97
- To **extend** the default rules in your own `commitlint.config.js`, you can start from the same preset:
177
+ To **extend** the preset in your own `commitlint.config.js`:
98
178
 
99
179
  ```js
100
180
  module.exports = {
@@ -105,12 +185,95 @@ module.exports = {
105
185
  };
106
186
  ```
107
187
 
108
- Programmatic access to shared constants (types, line limits) is available via:
188
+ Shared constants (types, line limits):
109
189
 
110
190
  ```js
111
191
  const rules = require("@verndale/ai-commit/rules");
112
192
  ```
113
193
 
194
+ ---
195
+
196
+ ## GitHub Actions (CI snippet)
197
+
198
+ Use **commitlint in your own workflow file** — nothing calls back to the `ai-commit` repository’s pipelines. After `pnpm add -D @verndale/ai-commit`, add a root **`commitlint.config.cjs`** (or `.js`) that **`extends: ["@verndale/ai-commit"]`** as in [commitlint without a second install](#commitlint-without-a-second-install). **`@commitlint/cli`** is already a dependency of this package, so `pnpm exec commitlint` works once dependencies are installed.
199
+
200
+ Save as **`.github/workflows/commitlint.yml`** (or merge the job into an existing workflow). Adjust **`branches`** / **`branches-ignore`** if your default branch is not **`main`**.
201
+
202
+ ```yaml
203
+ name: Commit message lint
204
+
205
+ on:
206
+ pull_request:
207
+ branches: [main]
208
+ types: [opened, synchronize, reopened, edited]
209
+ push:
210
+ branches-ignore:
211
+ - main
212
+
213
+ jobs:
214
+ commitlint:
215
+ runs-on: ubuntu-latest
216
+ steps:
217
+ - name: Checkout
218
+ uses: actions/checkout@v4
219
+ with:
220
+ fetch-depth: 0
221
+
222
+ - name: Setup Node
223
+ uses: actions/setup-node@v4
224
+ with:
225
+ node-version: "24.14.0"
226
+
227
+ - name: Enable pnpm via Corepack
228
+ run: corepack enable && corepack prepare pnpm@10.11.0 --activate
229
+
230
+ - name: Get pnpm store path
231
+ id: pnpm-cache
232
+ run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
233
+
234
+ - name: Cache pnpm store
235
+ uses: actions/cache@v4
236
+ with:
237
+ path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
238
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
239
+ restore-keys: |
240
+ ${{ runner.os }}-pnpm-store-
241
+
242
+ - name: Install dependencies
243
+ run: pnpm install --frozen-lockfile
244
+
245
+ - name: Lint PR title (squash merge becomes the commit on main)
246
+ if: github.event_name == 'pull_request'
247
+ env:
248
+ PR_TITLE: ${{ github.event.pull_request.title }}
249
+ run: |
250
+ printf '%s\n' "$PR_TITLE" | pnpm exec commitlint --verbose
251
+
252
+ - name: Lint commit messages (PR range)
253
+ if: github.event_name == 'pull_request'
254
+ run: |
255
+ pnpm exec commitlint \
256
+ --from "${{ github.event.pull_request.base.sha }}" \
257
+ --to "${{ github.event.pull_request.head.sha }}" \
258
+ --verbose
259
+
260
+ - name: Lint last commit (push)
261
+ if: github.event_name == 'push'
262
+ run: |
263
+ pnpm exec commitlint --from=HEAD~1 --to=HEAD --verbose
264
+ ```
265
+
266
+ **Notes**
267
+
268
+ | Topic | Detail |
269
+ | --- | --- |
270
+ | **Node** | Use a version that satisfies this package’s **`engines.node`** (see [Requirements](#requirements)). |
271
+ | **npm or Yarn** | Replace the Corepack + pnpm steps with your install (`npm ci`, `yarn install --immutable`, etc.) and run **`npx --no commitlint`** or **`yarn exec commitlint`** instead of **`pnpm exec commitlint`**. |
272
+ | **Config path** | If commitlint does not find your config (non-root monorepo, unusual filename), add **`--config path/to/commitlint.config.cjs`** to each **`commitlint`** invocation. |
273
+ | **Alignment with hooks** | The same rules apply as for **`.husky/commit-msg`** when it runs **`ai-commit lint --edit`** — both use the **`@verndale/ai-commit`** preset. |
274
+
275
+ ---
276
+
114
277
  ## Development (this repository)
115
278
 
116
279
  ```bash
@@ -118,46 +281,52 @@ corepack enable
118
281
  pnpm install
119
282
  ```
120
283
 
121
- Copy **`.env-example`** to `.env` and/or `.env.local` and set **`OPENAI_API_KEY`**. After staging, **`pnpm commit`** runs this repo’s CLI (`node ./bin/cli.js run`; the published package exposes `ai-commit` in `node_modules/.bin` for dependents). Hooks under `.husky/` call **`pnpm exec ai-commit`** from this checkout.
284
+ Copy **`.env-example`** to `.env` / `.env.local` and set **`OPENAI_API_KEY`**. After staging, **`pnpm commit`** runs **`node ./bin/cli.js run`**; published installs use the **`ai-commit`** binary under **`node_modules/.bin`**. Local **`.husky`** hooks use **`pnpm exec ai-commit`**.
122
285
 
123
286
  ### Repository automation
124
287
 
288
+ To run the same style of checks in **another** repository, copy the workflow in [GitHub Actions (CI snippet)](#github-actions-ci-snippet) (self-contained YAML; no call into this repo’s Actions).
289
+
125
290
  | Workflow | Trigger | Purpose |
126
291
  | --- | --- | --- |
127
- | [`.github/workflows/commitlint.yml`](./.github/workflows/commitlint.yml) | PRs to `main`, pushes to non-`main` branches | Commitlint on PR range or last push commit |
128
- | [`.github/workflows/pr.yml`](./.github/workflows/pr.yml) | Pushes (not `main`) and `workflow_dispatch` | Install deps, run **`pnpm run pr:create`** ([**`@verndale/ai-pr`**](https://www.npmjs.com/package/@verndale/ai-pr)) set **`PR_HEAD_BRANCH`** / **`PR_BASE_BRANCH`** in CI via env (workflow sets them). Use a PAT secret **`PR_BOT_TOKEN`** if branch protection requires it; otherwise document your org’s policy. |
129
- | [`.github/workflows/release.yml`](./.github/workflows/release.yml) | Push to **`main`** (including when a PR merges) | **`semantic-release`** — version bump, `CHANGELOG.md`, git tag, npm publish (with provenance), GitHub Release |
292
+ | [`.github/workflows/commitlint.yml`](./.github/workflows/commitlint.yml) | PRs to `main`, pushes to non-`main` | Commitlint on PR range or last push |
293
+ | [`.github/workflows/pr.yml`](./.github/workflows/pr.yml) | Pushes (not `main`), `workflow_dispatch` | **`pnpm run pr:create`** ([**`@verndale/ai-pr`**](https://www.npmjs.com/package/@verndale/ai-pr)); workflow sets **`PR_HEAD_BRANCH`** / **`PR_BASE_BRANCH`**. Use secret **`PR_BOT_TOKEN`** if branch protection requires it. |
294
+ | [`.github/workflows/release.yml`](./.github/workflows/release.yml) | Push to **`main`** | **semantic-release** — version, `CHANGELOG.md`, tag, npm publish, GitHub Release |
295
+
296
+ **Local `pnpm run pr:create`:** set **`GH_TOKEN`** / **`GITHUB_TOKEN`** and **`PR_BASE_BRANCH`** / **`PR_HEAD_BRANCH`** as needed.
130
297
 
131
- Optional **`pnpm run pr:create`** locally: set **`GH_TOKEN`** (or **`GITHUB_TOKEN`**) and branch overrides **`PR_BASE_BRANCH`** / **`PR_HEAD_BRANCH`** as needed.
298
+ ---
132
299
 
133
300
  ## Publishing (maintainers)
134
301
 
135
- Releases are automated with **[semantic-release](https://github.com/semantic-release/semantic-release)** on every push to **`main`** (see [`.releaserc.json`](./.releaserc.json) and [`tools/semantic-release-notes.cjs`](./tools/semantic-release-notes.cjs)).
302
+ Releases run via **[semantic-release](https://github.com/semantic-release/semantic-release)** on push to **`main`** ([`.releaserc.json`](.releaserc.json), [`tools/semantic-release-notes.cjs`](./tools/semantic-release-notes.cjs)).
136
303
 
137
304
  ### Secrets and registry
138
305
 
139
- - **`NPM_TOKEN`** (repository or organization secret) — must be able to **`npm publish`** this package in CI **without** an interactive one-time password. The Release workflow sets both `NPM_TOKEN` and `NODE_AUTH_TOKEN` from it.
140
- - **If the job fails with `EOTP` / “This operation requires a one-time password”:** the account has **2FA** that applies to publishes, and the token is not allowed to bypass OTP in CI. Fix it in one of these ways:
141
- - **Classic token:** npmjs.com → **Access Tokens** → **Generate New Token** (classic) → type **Automation** (not “Publish”). Store it as **`NPM_TOKEN`**. Automation tokens are for CI and skip OTP on publish.
142
- - **Granular token:** **New Granular Access Token** → turn on **Bypass two-factor authentication (2FA)**. Under **Packages and scopes**, set permissions to **Read and write** for **`@verndale/ai-commit`** (not “No access”). Leave **Allowed IP ranges** empty unless your org requires it—GitHub Actions egress IPs are not a single fixed range. Copy the token into **`NPM_TOKEN`**.
143
- - Alternatively, finish **[Trusted Publishing](https://docs.npmjs.com/trusted-publishers)** for this repo and package so OIDC can authorize publishes; you may still need a compatible token or npm-side setup until that path is fully enabled—see npm’s docs for your account type.
144
- - **`GITHUB_TOKEN`** — provided by Actions for API calls (GitHub Release, etc.). The checkout and git plugin use **`SEMANTIC_RELEASE_TOKEN`** when set; otherwise they use `GITHUB_TOKEN` (see below).
306
+ - **`NPM_TOKEN`** (repo or org secret) — must **`npm publish`** in CI **without** an interactive OTP. The Release workflow sets **`NPM_TOKEN`** and **`NODE_AUTH_TOKEN`** from it.
307
+ - **If the job fails with `EOTP` / “one-time password”:** 2FA is enforced on publish and the token cannot skip OTP. Fix in one of these ways:
308
+ - **Classic token:** [npmjs.com](https://www.npmjs.com/) → **Access Tokens** → **Generate New Token** (classic) → type **Automation** (not “Publish”). Store as **`NPM_TOKEN`**.
309
+ - **Granular token:** **New Granular Access Token** → enable **Bypass two-factor authentication (2FA)**. Under **Packages and scopes**, **Read and write** for **`@verndale/ai-commit`**. Leave **Allowed IP ranges** empty unless required (Actions egress is not a single fixed IP).
310
+ - Or finish **[Trusted Publishing](https://docs.npmjs.com/trusted-publishers)** for this repo and package (OIDC); you may still need npm-side setup—see npm’s docs for your account.
311
+ - **`GITHUB_TOKEN`** — Provided by Actions. Checkout and **`@semantic-release/git`** use **`SEMANTIC_RELEASE_TOKEN`** when set; otherwise **`GITHUB_TOKEN`**.
145
312
 
146
- **npm provenance:** [`.releaserc.json`](./.releaserc.json) sets `"provenance": true` on `@semantic-release/npm`, which matches **npm Trusted Publishing** from this GitHub repository. On [npmjs.com](https://www.npmjs.com/), enable **Trusted Publishing** for this package linked to **`verndale/ai-commit`** (or your fork’s repo if you test there). If publish fails until that is configured, either finish Trusted Publishing setup or temporarily set `"provenance": false` in `.releaserc.json` (you lose the provenance badge).
313
+ **npm provenance:** [`.releaserc.json`](.releaserc.json) sets **`"provenance": true`** on **`@semantic-release/npm`**, which matches **npm Trusted Publishing** from this GitHub repo. On [npmjs.com](https://www.npmjs.com/), enable **Trusted Publishing** for this package linked to **`verndale/ai-commit`** (or your fork). If publish fails until that works, finish Trusted Publishing or temporarily set **`"provenance": false`** in **`.releaserc.json`** (you lose the provenance badge).
147
314
 
148
- ### Branch protection and release commits
315
+ ### Branch protection
149
316
 
150
- semantic-release pushes a **release commit** and **tag** back to `main` via `@semantic-release/git`. If **`main`** is protected and the default token cannot push, either allow **GitHub Actions** to bypass protection for this repository, or add a personal access token (classic: `repo`, or fine-grained: **Contents** read/write on this repo) as **`SEMANTIC_RELEASE_TOKEN`**. The Release workflow passes `SEMANTIC_RELEASE_TOKEN || GITHUB_TOKEN` to checkout and to semantic-release as `GITHUB_TOKEN`.
317
+ semantic-release pushes a **release commit** and **tag** back to **`main`** via **`@semantic-release/git`**. If **`main`** is protected and the default token cannot push, either allow **GitHub Actions** to bypass protection for this repository, or add a PAT (classic: **`repo`**; fine-grained: **Contents** read/write) as **`SEMANTIC_RELEASE_TOKEN`**. The Release workflow passes **`SEMANTIC_RELEASE_TOKEN || GITHUB_TOKEN`** to checkout and to semantic-release as **`GITHUB_TOKEN`**.
151
318
 
152
319
  ### Commits that produce releases
153
320
 
154
- **Conventional Commits** on `main` drive `@semantic-release/commit-analyzer` (patch / minor / major). The analyzer uses the **first line** of each commit since the last tag; long PR bodies do not substitute for a releasable header.
321
+ **Conventional Commits** on **`main`** drive the analyzer (patch / minor / major) using each commit’s **first line**; PR bodies do not replace that.
322
+
323
+ With default [`.releaserc.json`](.releaserc.json) (no custom **`releaseRules`**), types like **`chore`**, **`docs`**, **`ci`**, **`style`**, **`test`**, **`build`** do **not** bump version or update **`CHANGELOG.md`**. For user-facing releases, use **`feat`**, **`fix`**, **`perf`**, **`revert`**, or breaking markers. With **squash merge**, the merged message is usually the **PR title**—keep it commitlint-clean.
155
324
 
156
- With the default plugin configuration in [`.releaserc.json`](./.releaserc.json) (no custom `releaseRules`), commits whose type is only **`chore`**, **`docs`**, **`ci`**, **`style`**, **`test`**, **`build`**, etc. **do not** trigger a version bump, `CHANGELOG.md` update, or tag. To ship semver for user-facing work, use a squash **PR title** (or merge commit message) with a releasable type—typically **`feat`**, **`fix`**, **`perf`**, or **`revert`**, or a **breaking** change (`!` / `BREAKING CHANGE:`). For **squash merge**, the merged commit message is usually the **PR title**, so match commitlint there. PR checks lint the PR title and the commits on the branch.
325
+ To release from **`chore`**/**`docs`**-only merges, maintainers can add **`releaseRules`** to **`@semantic-release/commit-analyzer`** in **`.releaserc.json`**; the default skips those types so releases stay signal-heavy.
157
326
 
158
- If the project ever needs patch releases from `chore`/`docs`-only merges, maintainers can add **`releaseRules`** to `@semantic-release/commit-analyzer` in `.releaserc.json`; the default is to skip those types so releases stay signal-heavy.
327
+ Tag-only npm publish was removed in favor of this flow to avoid double publishes. **Local try:** `pnpm release` (needs tokens and git state; use a fork or **`--dry-run`** as appropriate).
159
328
 
160
- Tag-only npm publish was removed in favor of this flow to avoid double publishes. To try a release locally: `pnpm release` (requires appropriate tokens and git state; use a fork or `--dry-run` as appropriate).
329
+ ---
161
330
 
162
331
  ## License
163
332
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verndale/ai-commit",
3
- "version": "2.4.3",
3
+ "version": "2.5.0",
4
4
  "description": "AI-assisted conventional commits with bundled commitlint — one install, aligned rules",
5
5
  "license": "MIT",
6
6
  "author": "Verndale",