Branch and Commit Policy¶
Authoritative source: /Users/pk/ws/CLAUDE.md (workspace rules 4, 7, 8, 27).
This doc codifies those rules for every QuantaTrade repo and ships the hooks
that enforce them locally.
TL;DR¶
| Rule | What it looks like |
|---|---|
| Branch | <type>/<issue-num>-<slug> (e.g. perf/95-save-seq-state-async) |
| Hotfix branch | fix/<slug> (no issue number — only for ≤20 LOC, ≤2 files) |
| Commit subject | <type>: <imperative subject>, ≤70 chars |
| Commit body | Explains why, references the issue (Refs #N / Closes #N) |
| PR body | Single-line Closes #N so the issue auto-closes on merge |
| AI attribution | Never. No Co-Authored-By: Claude, no Generated with…, no robot emoji trailers |
Allowed types: feat, fix, perf, docs, chore, ci, security, design, refactor, test.
Why¶
- Discoverability. Anyone landing on a branch or PR can tell from the name alone what kind of change it is, which issue drives it, and whether it can ship through the hotfix lane.
- Auditability. External counterparties (auditors, escrow agents, the QT Labs client) need a clean log. No AI attribution in commits keeps the IP story unambiguous and avoids leaking tooling details into client-visible artefacts.
- Mechanical safety. A pre-push hook that rejects unconventional branch names catches the drift we saw in PRs #31–#37 before the bad name lands in the remote.
Rules in detail¶
Branch naming¶
<type>must be one of the allowed types above.<issue-num>is the GitHub issue number from the most-affected repo.<short-slug>is kebab-case, ≤40 chars. Stick to the verb-object pattern (save-seq-state-async, notmake-it-faster).- Hotfix / typo / docs-only edit (≤20 LOC, ≤2 files): issue optional; branch
may be
fix/<slug>ordocs/<slug>. - Long-lived branches
main,next,release/*are not feature branches — direct pushes are warned but allowed for emergency operators.
Commit messages¶
<type>: <imperative subject up to 70 chars>
<body — explains why, references prior decisions, links related issues>
Refs #<n> # or Closes #<n> on the final commit of the branch
- Subject is imperative (
add staking schema, notadded staking schema). - Body explains why. Bullet points are fine. Wrap at ~80 cols.
- Trailer references the issue.
Closes #Non the final commit closes the issue when the PR merges. - No AI attribution. This is non-negotiable per workspace rule 4. The
commit-msghook will reject any message containingClaude,Anthropic,Generated with,Co-Authored-By:,🤖, ornoreply@anthropic. The literal tokenCLAUDE.md(the workspace policy filename) is whitelisted so legitimate references like "per CLAUDE.md rule 4" still pass.
Pull requests¶
- Title = the eventual change as one line, same conventions as a subject.
- Body should include:
## Summary— 1–3 bullets## Why— link to the design / issue thread## Test plan— markdown checklistCloses #<n>on its own line- A template lives at
.github/PULL_REQUEST_TEMPLATE.mdin this docs repo; copy it into every code repo (one tiny PR per repo).
Issue-first workflow¶
Per workspace rule 27: open the issue before the non-trivial branch. The
issue title is the eventual PR title. Cross-repo work links via
Refs Wagglyn-Limited/<repo>#<n> (or the QT-equivalent).
How the hooks enforce this¶
Two hooks ship in scripts/git-hooks/:
| Hook | When | What it does |
|---|---|---|
commit-msg |
Every git commit |
Rejects bad subject prefix, >70-char subject, AI attribution markers |
pre-push |
Every git push |
Rejects feature branches that don't match the type-prefix pattern; warns on direct push to main / next / release/* |
Both hooks are BSD-compatible (grep / sed flags work on macOS without GNU coreutils).
What the hooks DO NOT do (yet)¶
- They do not check that
<issue-num>corresponds to a real open GitHub issue — that needs theghCLI and a token, and is better done in CI. Future enhancement: a CI job that runs on PR open and comments if the branch name has no matching issue. - They do not enforce
Closes #Nin the PR body — that's a server-side check best done with a GitHub Action. - They do not run on the remote, only locally. A developer who bypasses with
--no-verifyand pushes anyway will not be blocked. Mitigate with a branch-protection rule on the remote that requires the same pattern.
Installing the hooks in a repo¶
From inside any QT repo:
# If the repo has the docs repo checked out as a sibling:
../quantatrade/scripts/install-git-hooks.sh
# Or pass the docs-repo path explicitly:
/Users/pk/ws/quantatrade/scripts/install-git-hooks.sh /path/to/target/repo
The installer symlinks both hooks into the target repo's .git/hooks/. It is
idempotent: re-running replaces the symlinks but refuses to clobber a
hand-written hook file (move it aside first if you want the policy hook to
take over).
To uninstall:
Bypass — emergencies only¶
Both hooks honour git commit --no-verify and git push --no-verify. Per
workspace rule "Never skip hooks unless explicitly requested", this is allowed
only in genuine emergencies (e.g. production is on fire and the policy text
itself is broken). Document the bypass in the PR body when used.
Rollout¶
This doc repo is the source of truth. Each code repo adopts the policy by:
- Running
scripts/install-git-hooks.shonce per dev machine per repo. - Copying
.github/PULL_REQUEST_TEMPLATE.mdinto the code repo via a one-line PR (chore: adopt QT PR template). - Optionally adding a branch-protection rule on the remote that requires the same branch-name regex (server-side enforcement).
Open follow-ups¶
- CI-side branch-name check (GitHub Action) — depends on a token.
- CI-side
Closes #Nlinter for PR body. - Backfill: existing PRs #31–#37 are out of policy but already in flight; leave them, apply the policy to new work going forward (per workspace rule 27's "backfilling is optional" note).