# Contributing to passid
All contributors — maintainers and one-time patch authors alike — must follow
these rules. The CI pipeline enforces what it can; code review enforces the rest.
---
## Table of Contents
1. [Local Setup](#local-setup)
2. [Branching Model](#branching-model)
3. [Branch Naming](#branch-naming)
4. [Commit Messages](#commit-messages)
5. [Pull Requests](#pull-requests)
6. [Releasing](#releasing)
7. [Code Style](#code-style)
8. [Quick Reference](#quick-reference)
9. [Document History](#document-history)
---
## Local Setup
To contribute to `passid`, you will need to set up your local development environment.
1. **Install Rust:** Use [rustup](https://rustup.rs/) to install the latest stable Rust toolchain.
2. **Clone the repository:**
```sh
git clone https://github.com/sharma-vikram/passid.git
cd passid
```
3. **Verify the build and tests:**
```sh
cargo build
cargo test --all-features
```
---
## Branching Model
This project uses **trunk-based development** with one permanent branch:
| `main` | Always releasable. Tagged commits trigger CI releases. | Maintainers only for hotfixes; otherwise via PR |
All development happens on short-lived feature branches that are merged back
into `main` via pull request. There is no long-lived `develop` branch.
> **Rule:** `main` must always compile, pass all tests, and be safe to tag.
---
## Branch Naming
### Format
```
<type>/<scope>
```
- **`<type>`** — one of the prefixes in the table below (lowercase).
- **`<scope>`** — a short, lowercase, hyphen-separated description of the work.
If the work is tracked by a GitHub issue, prefix the scope with the issue
number: `<issue-number>-<description>`.
### Types
| `feat` | Adding a new user-facing feature | `feat/42-add-passphrase-mode` |
| `fix` | Fixing a bug in existing behaviour | `fix/37-off-by-one-min-length` |
| `hotfix` | Urgent patch applied directly to `main` | `hotfix/cvss-high-entropy-leak` |
| `chore` | Maintenance: deps, CI, build, toolchain | `chore/bump-rand-0.11` |
| `docs` | Documentation only, no production code change | `docs/improve-cli-examples` |
| `test` | Adding or fixing tests, no production code change | `test/rejection-sampling-edge-cases` |
| `refactor` | Code restructuring with no behaviour change | `refactor/extract-config-validation` |
| `perf` | Performance improvement with no behaviour change | `perf/reduce-pool-allocations` |
| `release` | Release preparation (version bump + changelog) | `release/1.2.0` |
### Rules
1. **All lowercase.** No uppercase letters anywhere in a branch name.
2. **Hyphens only.** Use `-` as the word separator; never underscores or spaces.
3. **No slashes except the type separator.** `feat/foo` is valid; `feat/foo/bar` is not.
4. **Keep it short.** The scope should be readable at a glance — aim for 3–5 words maximum.
5. **No personal identifiers.** Branch names are about the work, not the author.
`feat/vikram-new-flag` → `feat/add-verbose-flag`.
6. **Delete after merge.** Stale branches are noise. Delete the remote branch
when the PR is merged (GitHub can do this automatically).
### Anti-patterns
| `my-changes` | No type prefix, not descriptive | `fix/33-symbol-set-dedup` |
| `Feature/NewFlag` | Mixed case, wrong separator style | `feat/add-no-symbol-flag` |
| `wip` | Meaningless | `feat/15-count-flag` |
| `fix-stuff` | No type prefix | `fix/clamp-length-warning` |
| `main2` | Shadows the permanent branch name | use a descriptive `feat/…` or `chore/…` |
---
## Commit Messages
This project follows the [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) specification.
Conventional Commits keep the history readable and drive automated changelog
generation.
### Format
```
<type>(<optional scope>): <short imperative summary>
[optional body]
[optional footers]
```
### Rules
- **First line ≤ 72 characters.**
- **Use the imperative mood** in the summary: "add flag" not "added flag" nor "adds flag".
- **No period** at the end of the summary line.
- **Blank line** between the summary and any body.
- **Body** wraps at 80 characters and explains *what* and *why*, not *how*.
- **Breaking changes** are indicated by `!` after the type/scope, and/or a
`BREAKING CHANGE:` footer.
### Types (mirror branch types)
| `feat` | MINOR | Features |
| `fix` | PATCH | Bug Fixes |
| `hotfix` | PATCH | Bug Fixes |
| `perf` | PATCH | Performance |
| `refactor` | none | — |
| `test` | none | — |
| `docs` | none | — |
| `chore` | none | — |
| `build` | none | — |
| `ci` | none | — |
| `feat!` / `fix!` | MAJOR | ⚠ Breaking Changes |
### Examples
```
feat(cli): add --no-symbol flag for symbol exclusion
Closes #42. Some deployments prohibit symbols in passwords; this
flag allows users to opt out of the default symbol set without
having to omit --symbol (which was never included by default anyway).
```
```
fix: clamp --length to MIN_LENGTH instead of hard erroring
Silently accepting lengths below 10 produced weak passwords without
feedback. The previous hard error was overly strict for scripted use.
Clamping with a stderr warning is the right trade-off.
```
```
chore: bump rand 0.10 → 0.11
Updates IndexedRandom import path per upstream rename. No behaviour
change.
```
```
feat!: require at least one character set flag
BREAKING CHANGE: previously, omitting all flags generated an
uppercase+lowercase password by default. Now the caller must be
explicit. Users relying on the default must add no flags — behaviour
is unchanged — but scripts that depended on the old default message
format will need updating.
```
---
## Pull Requests
1. **Open against `main`.** There is no separate integration branch.
2. **One logical change per PR.** Mix neither unrelated fixes nor
reformatting with feature work — it makes review harder and bisection
harder.
3. **Title follows Conventional Commits format.** The PR title becomes the
squash-merge commit message: `feat(cli): add --no-symbol flag`.
4. **All CI checks must pass** before requesting review.
5. **Self-review first.** Read your own diff before asking someone else to.
6. **Address review comments with new commits** during review, then squash on
merge. Do not force-push during active review.
7. **Reference issues.** Use GitHub keywords in the PR body:
`Closes #42`, `Fixes #15`.
### Merge strategy
This project enforces a strict **Squash and Merge** policy for all pull requests.
> **Note:** This strict policy was enforced starting March 29, 2026 (post-v1.6.0). Earlier repository history may contain standard merge commits and duplicated release bump logging.
- **Why:** It condenses multi-commit PRs (WIPs, typo fixes, review adjustments) into a single, clean commit on `main`. This prevents duplicate merge commits and keeps the Git history strictly linear, making `git bisect` much easier.
- **How:** The maintainer will squash merge the PR. Ensure your PR title follows the Conventional Commits format exactly, as GitHub will use it for the final squashed commit message.
> Standard merge commits and rebase-merges are **not** used.
---
## Releasing
The full release runbook is in [doc/RELEASING.md](doc/RELEASING.md).
**Summary of the happy path:**
1. Create a `release/<version>` branch from `main`.
2. Bump the version in `Cargo.toml`; run `cargo build --release` to update `Cargo.lock`.
3. Commit: `chore: bump version to v<version>`
4. Open a PR → merge into `main`.
5. Tag the merge commit: `git tag v<version>`
6. Push tag: `git push origin v<version>`
7. CI builds all targets and publishes the GitHub Release automatically.
> Tags that match `v*.*.*` are the **only** release trigger.
> Never push a `v*.*.*` tag to a commit that hasn't passed CI on `main`.
---
## Code Style
All style checks run in CI. A PR with failing checks will not be merged.
| `rustfmt` | `cargo fmt --check` | Canonical Rust formatting |
| `clippy` | `cargo clippy --all-features -- -D warnings` | Lints (all + pedantic + nursery + cargo) |
| `cargo-audit` | `cargo audit` | Known vulnerability advisories |
| `cargo-deny` | `cargo deny check` | License and supply-chain checks |
Run everything locally before pushing:
```sh
cargo fmt
cargo clippy --all-features -- -D warnings
cargo test --all-features
cargo audit
cargo deny check
```
No `#[allow(...)]` attributes may be added without a comment explaining why
the lint is a false positive for this specific case.
---
## Quick Reference
```
Branch format: <type>/<issue-number?>-<short-description>
Commit format: <type>(<scope>?): <imperative summary>
Tag format: v<MAJOR>.<MINOR>.<PATCH>
Merge target: main (always)
Release trigger: git push origin v<version>
```
### Branch type → commit type mapping
| `feat/` | `feat` | MINOR |
| `fix/` | `fix` | PATCH |
| `hotfix/` | `hotfix` | PATCH |
| `chore/` | `chore` | none |
| `docs/` | `docs` | none |
| `test/` | `test` | none |
| `refactor/` | `refactor` | none |
| `perf/` | `perf` | PATCH |
| `release/` | `chore` | none |