Skip to content

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>/<issue-num>-<short-slug>
  • <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, not make-it-faster).
  • Hotfix / typo / docs-only edit (≤20 LOC, ≤2 files): issue optional; branch may be fix/<slug> or docs/<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, not added staking schema).
  • Body explains why. Bullet points are fine. Wrap at ~80 cols.
  • Trailer references the issue. Closes #N on the final commit closes the issue when the PR merges.
  • No AI attribution. This is non-negotiable per workspace rule 4. The commit-msg hook will reject any message containing Claude, Anthropic, Generated with, Co-Authored-By:, 🤖, or noreply@anthropic. The literal token CLAUDE.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 checklist
  • Closes #<n> on its own line
  • A template lives at .github/PULL_REQUEST_TEMPLATE.md in 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 the gh CLI 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 #N in 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-verify and 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:

rm /path/to/target/repo/.git/hooks/commit-msg /path/to/target/repo/.git/hooks/pre-push

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:

  1. Running scripts/install-git-hooks.sh once per dev machine per repo.
  2. Copying .github/PULL_REQUEST_TEMPLATE.md into the code repo via a one-line PR (chore: adopt QT PR template).
  3. 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 #N linter 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).