Git Practices
These are the conventions for all git operations in this project. Every branch, commit, and pull request follows these rules without exception.
Branch Naming
Format: <type>/<ticket-id>
Types:
- •
feat— New feature or capability - •
fix— Bug fix - •
refactor— Code restructuring without behavior change - •
chore— Maintenance, tooling, config, dependencies
Examples:
feat/CTR-12 fix/CTR-45 refactor/CTR-78 chore/CTR-99
Rules:
- •Ticket ID matches the project tracker (JIRA, Linear, etc.)
- •Lowercase everything
- •No descriptions in branch name — the ticket ID is the reference
- •One branch per ticket — don't combine work from multiple tickets
Commit Messages
Format:
<type>(<scope>): <short message> [<ticket-id>] <description>
Line 1 (subject):
- •
type— Same as branch types, plus:docs(documentation),test(tests only) - •
scope— One word indicating the module or layer affected (e.g.,auth,api,database,ui,config) - •
short message— Imperative mood, lowercase, no period, under 50 characters after the scope - •
ticket-id— In square brackets at the end
Line 2: Blank line (always)
Line 3+ (description):
- •What was done and why, in plain sentences
- •Reference related files or decisions if helpful
- •Keep it concise — enough context for someone reading git log, not a full essay
Examples:
feat(document): implement search by text [CTR-12] Added full-text search to the document module using the existing search index. Follows the pattern established in the user search feature.
fix(auth): prevent token refresh race condition [CTR-45] Two concurrent requests could both trigger a token refresh, causing one to fail with an invalid token error. Added a mutex to ensure only one refresh happens at a time.
refactor(api): extract validation middleware [CTR-78] Moved request validation logic from individual route handlers into a shared middleware. Reduces duplication across 12 endpoints.
chore(config): update dependencies to latest stable [CTR-99] Bumped all non-major dependencies. No breaking changes detected.
test(billing): add edge cases for prorated charges [CTR-33] Covers: mid-cycle upgrade, downgrade on last day, zero-usage month, and negative balance scenarios.
docs(api): update endpoint documentation for v2 routes [CTR-55] Reflects the new response format introduced in the API v2 migration.
Rules:
- •Subject line is mandatory, description is mandatory for anything beyond trivial changes
- •No co-author or AI attribution lines
- •One logical change per commit — if you have to use "and" in the subject, consider splitting
- •The type in the commit matches the branch type (a
feat/branch should havefeat()commits, with occasionaltest()ordocs()commits for supporting work)
Pull Requests
Title format: Same as commit subject line:
<type>(<scope>): <short message> [<ticket-id>]
Body format:
## Summary [2-4 sentences: what this PR does and why. Written for a reviewer who hasn't read the ticket — they should understand the change from this summary alone.] ## Changes - [Concrete change 1 — what file/module and what was done] - [Concrete change 2] - [Concrete change 3] ## Testing - [How this was verified — tests added, manual testing done] - [Specific scenarios tested] - [Edge cases covered] ## Ticket [TICKET-ID](link to ticket if available)
Examples:
Title: feat(document): implement search by text [CTR-12]
Body:
## Summary Adds full-text search to the document module, allowing users to search documents by content rather than just title. Uses the existing search index infrastructure established in user search. ## Changes - Added search endpoint `GET /api/documents/search` with query parameter - Created `DocumentSearchService` following the `UserSearchService` pattern - Added search result highlighting in the response - Added 8 unit tests covering: exact match, partial match, no results, special characters, pagination, and sorting ## Testing - Unit tests pass: `npm run test -- documents` - Manual testing: verified search returns expected results with 500+ documents - Tested edge cases: empty query, very long query, special characters ## Ticket [CTR-12](https://tracker.example.com/CTR-12)
Rules:
- •PR title follows the exact same format as commit messages
- •Summary explains the WHY, changes list the WHAT
- •Testing section is mandatory — no PRs without describing how it was verified
- •One ticket per PR — don't bundle unrelated work
- •Link the ticket if possible
- •No screenshots unless the change is visual (then they're encouraged)
Tool
Pull requests are created with the GitHub CLI (gh):
gh pr create --title "<title>" --body "<body>"
For drafts:
gh pr create --draft --title "<title>" --body "<body>"
Determining the Type
When in doubt about the commit/branch type:
| If the change... | Type |
|---|---|
| Adds a new user-facing capability | feat |
| Fixes something that was broken | fix |
| Changes code structure without changing behavior | refactor |
| Updates build, CI, deps, config, tooling | chore |
| Only adds or updates documentation | docs |
| Only adds or updates tests | test |
If a commit includes both a feature and its tests, use feat — the tests support the feature. If a commit is purely adding missing tests for existing code, use test.
Determining the Scope
The scope should be a single word identifying the affected module or layer. Use names that match your project's directory structure or domain concepts:
Examples of good scopes:
- •
auth,api,database,ui,config,billing,search,notifications - •
user,document,task,project,team
Avoid:
- •Generic scopes:
app,code,misc,stuff - •Multi-word scopes:
user-profile(useuserorprofile) - •File names as scopes:
userService(useuser)
Git Worktrees
We use git worktrees instead of switching branches. Each branch lives in its own directory, allowing parallel work across multiple features.
Folder convention:
my-repo/ ← main branch (cloned from GitHub) my-repo-worktrees/ ← worktree directory (sibling, auto-created) feat/CTR-12/ ← one worktree per branch fix/CTR-45/
Creating a worktree:
git wt feat/CTR-12
This uses the global alias git wt which:
- •Creates the worktree at
../{repo}-worktrees/{branch} - •Reuses the branch if it already exists, or creates it with
-b
Removing a worktree:
git wtr feat/CTR-12
This uses the global alias git wtr which removes the worktree from ../{repo}-worktrees/{branch}.
Listing worktrees:
git worktree list
Rules:
- •Always create worktrees from the main repo directory (not from inside another worktree)
- •Branch name follows the Branch Naming convention above:
<type>/<ticket-id> - •One worktree per ticket — matches one-branch-per-ticket rule
- •Remove worktrees after the PR is merged to keep the directory clean
- •The main repo directory always stays on the default branch (main/master) — never switch branches there
Backlog Lock
When working with worktrees, multiple sessions may access the backlog simultaneously. A lockfile prevents two worktrees from picking up the same item.
File: docs/backlog.lock
Format:
# Managed by /next and /pr commands — do not edit manually
locks:
- item: "S-003"
feature: "FEAT-007"
branch: "feat/CTR-12"
worktree: "../my-repo-worktrees/feat/CTR-12"
started: "2026-02-12T14:30:00"
Rules:
- •
/nextcreates a lock entry when picking up a backlog item - •
/prremoves the lock entry after the PR is created - •
/worktree --removechecks for stale locks and warns - •Before picking an item,
/nextchecks the lockfile — if the item is locked, it skips to the next - •The lockfile is committed to the repo so all worktrees can see it
- •If a lock is stale (branch no longer exists, worktree removed), it can be cleaned up