---
name: pr-workflow
description: Create pull requests for bluetooth-devices/dbus-fast. Use when creating PRs, submitting changes, or preparing contributions.
allowed-tools: Read, Bash, Glob, Grep
---

# dbus-fast PR Workflow

When creating a pull request for `bluetooth-devices/dbus-fast`,
follow these steps. Repo-wide conventions live in
[CLAUDE.md](../../../CLAUDE.md); this skill summarises the parts
that matter at PR-creation time.

## 1. Create branch from origin/main

`origin` points at `bluetooth-devices/dbus-fast`; there is no fork
in this workflow. Always re-fetch first so the branch is based on
the latest `main`:

```bash
git fetch origin
git checkout -b <branch-name> origin/main
```

## 2. Conventional Commits — PR title is what matters

The repo is squash-merge only with
`squash_merge_commit_title = PR_TITLE`, so the PR title becomes
the subject of the commit that lands on `main`. The
`pr-title.yml` workflow runs
`amannn/action-semantic-pull-request` against the title and is
the sole CI gate on its format — individual commits on the
branch are discarded by the squash, so CI no longer lints them.
The local `commitizen` pre-commit hook still flags per-commit
subjects at commit time as a convenience.

Accepted types: `build`, `chore`, `ci`, `docs`, `feat`, `fix`,
`perf`, `refactor`, `revert`, `style`, `test`. Scope is optional.
The subject (text after `type(scope):`) must start lowercase.
Examples that pass:

```
feat: add async context manager to MessageBus
fix(unmarshaller): handle empty arrays at end of frame
perf!: drop python 3.9 support
```

A mis-formatted PR title fails `pr-title.yml`; fix it in the
GitHub UI (or with `gh pr edit --title`) — no push is needed.

### Pick the type that matches the release impact

`python-semantic-release` reads the commit log on `main` to decide
the next version and write `CHANGELOG.md`:

| Type                                                               | Release effect                                  |
| ------------------------------------------------------------------ | ----------------------------------------------- |
| `feat:`                                                            | minor bump, shows in changelog under Features   |
| `fix:`, `perf:`                                                    | patch bump, shows under Bug Fixes / Performance |
| any with `!` or `BREAKING CHANGE:` footer                          | major bump                                      |
| `chore:`, `docs:`, `test:`, `ci:`, `style:`, `build:`, `refactor:` | no bump                                         |

A user-visible bugfix tagged `chore:` will be silently omitted
from the changelog; an internal refactor tagged `feat!:` will
mint a fake major release. Pick the type a changelog reader would
expect.

## 3. There is no PR template

The repo does not ship a `.github/PULL_REQUEST_TEMPLATE.md`, so
the PR body is freeform. Write a short description of:

- **What** the change does (one or two sentences of prose).
- **Why** — the motivating bug, benchmark, or feature ask.
- **Linked issues** — `fixes #N` / `closes #N` for issue
  closure, or a bare reference if the change is related but
  doesn't close.

Keep it focused; the commit messages carry the per-commit detail.

## 4. Commit hygiene

- **Imperative-mood subject line** — "Add X", not "Added X".
- **No `Co-Authored-By` trailers for LLM tools.** Project
  preference — commits attribute the human who reviewed the
  change.
- Let pre-commit run (ruff lint + format, pyupgrade,
  trailing-whitespace, etc.). If a hook auto-fixes a file, the
  commit aborts — re-stage the auto-fixed files and re-commit.
- Write tests for behavioural changes (under `tests/`). Run them
  under `dbus-run-session` since the suite needs a session bus:

  ```bash
  dbus-run-session -- poetry run pytest --timeout=5
  ```

## 5. Push and create the PR

```bash
git push -u origin <branch-name>
gh pr create --repo bluetooth-devices/dbus-fast --base main \
  --title "type(scope): lowercase subject under ~70 chars" \
  --body-file /tmp/pr-body.md
```

Use `--body-file` rather than `--body "..."` so that backticks,
asterisks, and other Markdown in the body are passed through
verbatim instead of being mangled by shell quoting.

## 6. After the PR is open

CI runs:

- `lint` — pre-commit (ruff lint + format, pyupgrade,
  trailing-whitespace).
- `pr-title` — PR-title Conventional Commits check (the only
  commit-format gate; individual commits are squashed away on
  merge).
- `test` matrix — Python 3.10–3.14 + `3.14t`, each in both
  `SKIP_CYTHON=1` and `REQUIRE_CYTHON=1` modes.
- `test_big_endian` — s390x via `uraimo/run-on-arch-action`,
  catches endian regressions in the marshaller/unmarshaller.
- `benchmark` — CodSpeed run for the hot paths.

A red `test_big_endian` job almost always means a byte-order
assumption leaked into the marshaller/unmarshaller — re-check
any `struct` format strings and endian conversions in
`src/dbus_fast/_private/`. A CodSpeed regression on a Cythonized
path is worth investigating before merge; if the regression is
intentional, call it out in the PR body so reviewers don't have
to guess.
