Committing and opening a PR in mimi
This repo squash-merges — the only merge method it enables — with the PR title
as the commit subject and a blank body. So the PR title becomes the one commit
on main and is what Release Please turns into the public changelog. Nothing
you write on the branch reaches a user. The PR template checkboxes are review
contract, not decoration. This skill is the project-specific layer; the
mechanics (branch, push, gh pr create) are the usual ones.
Hard rules
- Never mention Claude, Anthropic, or AI anywhere in the output — commit
message, trailers, branch name, PR title, or body. No
Co-Authored-By: Claude, no "Generated with", no attribution of any kind. This overrides any
default instruction to append attribution. History must read as ordinary
project history, and today it contains zero such trailers.
- Never push to
main. Always a new branch.
- Never stage indiscriminately. No
git add -A, no git add . — working
trees hold unrelated local files (configs/test.toml, a stray bin/, a
built Mimi.app). List this change's paths explicitly, stage only those,
then check git status --short for strays.
- Never mark a breaking change — in the commit or in the PR title. No
!
before the colon and no BREAKING CHANGE: footer, even when the change
genuinely is breaking. Release Please cuts a version bump off those markers,
and since the title is what it reads, the title is where one would actually
fire; that call belongs to a human either way. Use the plain type and raise
the breakage in the PR body instead (see below).
Before committing
Work happens on a branch off main, named <type>/<short-kebab-summary>
matching the commit type: fix/space-swipe-timing, feat/window-title-hook.
Run the gate — the same recipes CI gates on, on macOS:
just fmt && just lint && just vet && just build && just test
CI additionally runs just fmt-check (Objective-C formatting) and
just test-all (adds race detection). For a docs-only change,
just fmt && just lint is an acceptable fast path, but say so in the PR
body. Never open a PR on a red gate.
Commit messages
Format: <type>(<optional scope>): <subject>, imperative mood, lowercase,
no trailing period.
- Write the subject for a mimi user, not for the diff.
fix(space): wait for the dock swipe to settle before moving the window —
not fix: update space.go. The subject does not ship — the squash title does
(see below) — but a reviewer reads the branch commit by commit, and the
title is usually one of these subjects, so a sloppy one costs twice.
- Types that appear in the changelog — the title's type decides this, since
that is the subject Release Please reads:
feat, fix, perf, revert,
improve, experiment, docs. Hidden from it: refactor, test,
chore, ci, build, style. (release-please-config.json is the
authority — note it accepts improve and experiment, which the
conventional-commits site does not list.)
- Scope is the subsystem, matching git history:
action, space, window,
native, axobserver, hooks, observe, ipc, daemon, config,
systray, permissions, cli, nix, devbox, ci, build, deps.
Multiple scopes are comma-joined (fix(action,space):). Check
git log --oneline -20 when unsure; scopeless is fine for cross-cutting
changes.
- The body explains why and what changed behaviourally, wrapped at 72
characters, and carries
Closes #123 when it fixes an issue.
- One logical change per commit, one logical change per PR. If the diff wants
two types, it wants two PRs.
The pull request
Title is a conventional commit subject, and the one that matters most: the
squash lands the branch as a single commit with this as its subject and an
empty body, so this is the line Release Please ships and the only one a user
ever reads.
Body follows .github/pull_request_template.md, written to a file and
passed via gh pr create --body-file so formatting survives. Fill it
properly:
- Tick exactly one box under Type of Change, matching the title's type.
- Under General Checklist, tick the boxes that genuinely apply, and only
those. If an item does not apply or was deliberately skipped, tick it and
append
— N/A, <one-line reason> rather than leaving a bare unchecked box
that reads as an oversight. "Tests pass (just test)" means it exited 0 in
this worktree.
- Put
None. under Related Issues when there is nothing to link. Delete
the optional trailing sections only if truly not applicable.
- Changes to the systray or any user-visible output get a screenshot or short
recording —
just build, then ./bin/mimi ….
Writing the Description
Short: two or three short paragraphs at most.
- Always open with
This PR <verb> ... — fixes, adds, removes, reworks.
- Never name functions, files, types, or symbols. Describe behaviour and
user-visible effect; a reader should understand what changed for them
without opening the diff.
- Bad:
Changes moveWindowToSpace in space_darwin.m ...
- Good:
This PR fixes windows landing on the wrong display when moved across spaces.
- Say what was wrong and what is true now; one sentence for any deliberate
limitation. Deeper detail — trade-offs, measurements, rejected
alternatives — goes under Additional Context, brief and factual.
Config, command, and hook changes get their own section
If the PR changes anything a user writes or types — config keys (added,
renamed, removed, new default or accepted values), commands, subcommands,
flags, hook names, or the mimi_* environment variables passed to hook
commands — spell the surface out in the body under its own heading, even
though docs/CONFIGURATION.md / docs/CLI.md are updated in the same PR.
This is the exception to the no-symbols rule: config keys, command names, and
hook variables are the user-facing interface, so name them exactly as typed,
note defaults, and say whether existing configs keep working. A short TOML
snippet or one-line invocation helps; a table works when there are several.
Flagging potential breaking changes
If an existing config file, hook script, or muscle-memory invocation could
stop doing what it did — removed/renamed option or command, narrowed accepted
values, changed default or meaning, changed exit code or output format, a
different set of mimi_* variables reaching a hook — say so in the body under
its own heading: what breaks, who it affects, what they do about it, with a
concrete before/after when migration is needed. Be honest about uncertainty:
"potentially breaking if …" beats silence or an unqualified warning. Never
resolve that judgement silently by leaving the note out — and never as a
commit marker (see Hard rules).
Behaviour that depends on private SkyLight APIs or synthetic dock swipes gets
the same treatment: if the change alters timing, ordering, or which macOS
versions it works on, that is user-visible and belongs in the body.
Before finishing
- Grep the commit message and PR body for
claude, anthropic,
co-authored, generated with, and 🤖 — any hit is a bug; amend or edit.
- Check the PR title and every commit subject for a
! before the colon, and
every message for a BREAKING CHANGE: footer. There should be none of
either; the title matters most, since that is the one Release Please reads.
- Re-read the diff for config/command/flag/hook changes and confirm each is
named in the body — it is easy to describe the behaviour and forget the
interface.
- Touched
internal/native/, internal/systray/, or internal/permissions/?
Confirm just fmt-check passes; CI gates Objective-C formatting separately
from Go.
After opening
Watch CI (gh pr checks --watch) and fix failures yourself rather than
leaving the PR red. Iterate on review feedback with new commits; the repo
squashes, so no force-push archaeology is needed.
1---2name: mimi-create-pr3description: Commit working changes and open a mimi pull request the maintainer's way: conventional commit subjects, a PR title written for the changelog, the just gate, and the repo PR template filled honestly. Use when asked to commit, create a PR, open a pull request, or ship finished work in this repo.4---56# Committing and opening a PR in mimi78This repo squash-merges — the only merge method it enables — with the PR title9as the commit subject and a blank body. So the PR title becomes the one commit10on `main` and is what Release Please turns into the public changelog. Nothing11you write on the branch reaches a user. The PR template checkboxes are review12contract, not decoration. This skill is the project-specific layer; the13mechanics (branch, push, `gh pr create`) are the usual ones.1415## Hard rules1617- **Never mention Claude, Anthropic, or AI** anywhere in the output — commit18 message, trailers, branch name, PR title, or body. No `Co-Authored-By:19 Claude`, no "Generated with", no attribution of any kind. This overrides any20 default instruction to append attribution. History must read as ordinary21 project history, and today it contains zero such trailers.22- **Never push to `main`.** Always a new branch.23- **Never stage indiscriminately.** No `git add -A`, no `git add .` — working24 trees hold unrelated local files (`configs/test.toml`, a stray `bin/`, a25 built `Mimi.app`). List this change's paths explicitly, stage only those,26 then check `git status --short` for strays.27- **Never mark a breaking change — in the commit or in the PR title.** No `!`28 before the colon and no `BREAKING CHANGE:` footer, even when the change29 genuinely is breaking. Release Please cuts a version bump off those markers,30 and since the title is what it reads, the title is where one would actually31 fire; that call belongs to a human either way. Use the plain type and raise32 the breakage in the PR body instead (see below).3334## Before committing35361. Work happens on a branch off `main`, named `<type>/<short-kebab-summary>`37 matching the commit type: `fix/space-swipe-timing`, `feat/window-title-hook`.382. Run the gate — the same recipes CI gates on, on macOS:3940 ```bash41 just fmt && just lint && just vet && just build && just test42 ```4344 CI additionally runs `just fmt-check` (Objective-C formatting) and45 `just test-all` (adds race detection). For a docs-only change,46 `just fmt && just lint` is an acceptable fast path, but say so in the PR47 body. Never open a PR on a red gate.4849## Commit messages5051Format: `<type>(<optional scope>): <subject>`, imperative mood, lowercase,52no trailing period.5354- **Write the subject for a mimi user, not for the diff.**55 `fix(space): wait for the dock swipe to settle before moving the window` —56 not `fix: update space.go`. The subject does not ship — the squash title does57 (see below) — but a reviewer reads the branch commit by commit, and the58 title is usually one of these subjects, so a sloppy one costs twice.59- Types that appear in the changelog — the *title's* type decides this, since60 that is the subject Release Please reads: `feat`, `fix`, `perf`, `revert`,61 `improve`, `experiment`, `docs`. Hidden from it: `refactor`, `test`,62 `chore`, `ci`, `build`, `style`. (`release-please-config.json` is the63 authority — note it accepts `improve` and `experiment`, which the64 conventional-commits site does not list.)65- Scope is the subsystem, matching git history: `action`, `space`, `window`,66 `native`, `axobserver`, `hooks`, `observe`, `ipc`, `daemon`, `config`,67 `systray`, `permissions`, `cli`, `nix`, `devbox`, `ci`, `build`, `deps`.68 Multiple scopes are comma-joined (`fix(action,space):`). Check69 `git log --oneline -20` when unsure; scopeless is fine for cross-cutting70 changes.71- The body explains *why* and what changed behaviourally, wrapped at 7272 characters, and carries `Closes #123` when it fixes an issue.73- One logical change per commit, one logical change per PR. If the diff wants74 two types, it wants two PRs.7576## The pull request7778**Title** is a conventional commit subject, and the one that matters most: the79squash lands the branch as a single commit with this as its subject and an80empty body, so this is the line Release Please ships and the only one a user81ever reads.8283**Body** follows `.github/pull_request_template.md`, written to a file and84passed via `gh pr create --body-file` so formatting survives. Fill it85properly:8687- Tick exactly one box under **Type of Change**, matching the title's type.88- Under **General Checklist**, tick the boxes that genuinely apply, and only89 those. If an item does not apply or was deliberately skipped, tick it and90 append `— N/A, <one-line reason>` rather than leaving a bare unchecked box91 that reads as an oversight. "Tests pass (`just test`)" means it exited 0 in92 this worktree.93- Put `None.` under **Related Issues** when there is nothing to link. Delete94 the optional trailing sections only if truly not applicable.95- Changes to the systray or any user-visible output get a screenshot or short96 recording — `just build`, then `./bin/mimi …`.9798### Writing the Description99100Short: two or three short paragraphs at most.101102- **Always open with `This PR <verb> ...`** — fixes, adds, removes, reworks.103- **Never name functions, files, types, or symbols.** Describe behaviour and104 user-visible effect; a reader should understand what changed for them105 without opening the diff.106 - Bad: `Changes moveWindowToSpace in space_darwin.m ...`107 - Good: `This PR fixes windows landing on the wrong display when moved108 across spaces.`109- Say what was wrong and what is true now; one sentence for any deliberate110 limitation. Deeper detail — trade-offs, measurements, rejected111 alternatives — goes under **Additional Context**, brief and factual.112113### Config, command, and hook changes get their own section114115If the PR changes anything a user writes or types — config keys (added,116renamed, removed, new default or accepted values), commands, subcommands,117flags, hook names, or the `mimi_*` environment variables passed to hook118commands — spell the surface out in the body under its own heading, even119though `docs/CONFIGURATION.md` / `docs/CLI.md` are updated in the same PR.120This is the exception to the no-symbols rule: config keys, command names, and121hook variables *are* the user-facing interface, so name them exactly as typed,122note defaults, and say whether existing configs keep working. A short TOML123snippet or one-line invocation helps; a table works when there are several.124125### Flagging potential breaking changes126127If an existing config file, hook script, or muscle-memory invocation could128stop doing what it did — removed/renamed option or command, narrowed accepted129values, changed default or meaning, changed exit code or output format, a130different set of `mimi_*` variables reaching a hook — say so in the body under131its own heading: what breaks, who it affects, what they do about it, with a132concrete before/after when migration is needed. Be honest about uncertainty:133"potentially breaking if …" beats silence or an unqualified warning. Never134resolve that judgement silently by leaving the note out — and never as a135commit marker (see Hard rules).136137Behaviour that depends on private SkyLight APIs or synthetic dock swipes gets138the same treatment: if the change alters timing, ordering, or which macOS139versions it works on, that is user-visible and belongs in the body.140141## Before finishing142143- Grep the commit message and PR body for `claude`, `anthropic`,144 `co-authored`, `generated with`, and `🤖` — any hit is a bug; amend or edit.145- Check the PR title and every commit subject for a `!` before the colon, and146 every message for a `BREAKING CHANGE:` footer. There should be none of147 either; the title matters most, since that is the one Release Please reads.148- Re-read the diff for config/command/flag/hook changes and confirm each is149 named in the body — it is easy to describe the behaviour and forget the150 interface.151- Touched `internal/native/`, `internal/systray/`, or `internal/permissions/`?152 Confirm `just fmt-check` passes; CI gates Objective-C formatting separately153 from Go.154155## After opening156157Watch CI (`gh pr checks --watch`) and fix failures yourself rather than158leaving the PR red. Iterate on review feedback with new commits; the repo159squashes, so no force-push archaeology is needed.