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.
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.
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.