Skip to content

Git Workflow


1. Branches

Branch Purpose
main Always builds, always passes tests. Never committed to directly.
feat/<module>-<thing> A feature
fix/<module>-<thing> A bug fix
docs/<area> Documentation only
refactor/<area> Behaviour-preserving change

One branch per logical change. If a branch needs "and" to describe it, split it.


2. Commits

Small and focused. A commit is one logical change that leaves the tree building.

Format

<type>(<scope>): <subject, imperative, lower case, no full stop>

<body: what changed and WHY. Wrap at 80. Include findings, not just actions.>

Co-Authored-By: Claude <noreply@anthropic.com>
Type For
feat New behaviour
fix Bug fix
refactor No behaviour change
perf Performance
docs Documentation
test Tests
build Build, dependencies, tooling
chore Everything else

Scope is the module: bulletin, character, core, economy, interaction, inventory, items, ownership, ui, world, legacy, or — once built, per ROADMAP.mdcrime, jobs, needs, chat, commands. Same taxonomy as the area labels in 05_ISSUE_TRACKING.md §3.

feat(character): implement identity and permission resolution
feat(items): add base item component
fix(save): prevent duplicate inventories on reconnect
refactor(network): reduce RPC usage in the door flow
docs(inventory): update serialization notes
test(economy): cover door tax repossession

The body matters

The subject says what. The body says why, and records anything the next reader would otherwise have to rediscover:

fix(inventory): enforce per-item stack limits

The legacy implementation read item.max and item.plural, but every definition
sets ITEM.Max and ITEM.Plural. Lua is case-sensitive, so the branch was dead
code and no item has ever had a working stack limit -- see D-03.

Adds ItemDefinition.MaximumStack as a typed property so the casing class of
bug cannot recur, plus a regression test named for the defect.

3. Before you push

Cadence. Commit locally after every logically complete piece — small and often, per §2. Push to origin at least once per milestone (see ROADMAP.md) or logical unit of work within one, whichever is more frequent, so origin/main never falls far enough behind local main that a lost machine would lose real work. Do not batch a milestone's worth of commits into a single end-of-session push.

  • [ ] Builds with no warnings
  • [ ] dotnet test passes
  • [ ] Documentation updated in the same commits — XML docs, module README, Architecture/
  • [ ] An ADR exists if an architectural decision was made
  • [ ] Self-reviewed the diff line by line
  • [ ] No commented-out code, no stray TODO without an owner, no debug logging

4. Merging

Squash if the branch is a messy sequence of one idea. Preserve history if the commits are each meaningful and independently buildable — that is the preferred outcome and the reason for committing in small pieces.

main must never be broken. If it is, fixing it takes priority over everything else.


5. Tags and releases

Tag milestones, not commits.

v0.1.0   Core framework
v0.2.0   Persistence
v0.3.0   Character
v0.4.0   Inventory and items
v0.5.0   First playable
v9.8.0   Release-prep (Milestone 10) -- everything short of a live server, done for real
v9.9.0   Player presence & admin tools (Milestone 11) -- pawn binding, teleport/noclip/
         spectate, real ban records and mute enforcement
v1.0.0   The first stable, feature-complete release

v1.0.0 means the first stable, feature-complete release, not "the next number after v0.5.0." It originally also marked the repository going public, but ADR-0008 moved the public launch up to Milestone 10, specifically to host a public documentation website — the repository is public now, well ahead of v1.0.0. The v9.x range for pre-launch hardening tags is kept anyway: renumbering already-released tags is worse than an explanation that's evolved, and the range still usefully signals "hardening, not the stable release" even though it no longer counts down to a visibility change. If more hardening lands after v9.8.0, it continues in the same range (v9.9.0, and so on) until v1.0.0 is actually cut.

GitHub Releases are for milestones only — major versions, public previews, release candidates, and v1.0. Nightly development stays on git with no release attached. v9.8.0 and any further v9.x tags do not get a GitHub Release on their own, even though the repository is now public — a Release is for announcing something to an audience, and pre-v1.0.0 hardening tags aren't that moment.


6. What never enters history

  • legacy/content/ — 162 MB of copyrighted Source 1 assets. Gitignored, and it stays that way.
  • CLAUDE.md, tasks.md, handoverdaily.md, and the rest of .claude/ — assistant session artifacts, centralized in a separate private claude-configs repo per the global assistant instructions. Gitignored; CLAUDE.md is a symlink back to the real file so tooling that expects it at the repo root still works. Now the repository is public (ADR-0008) this matters more than it did — none of this is meant for an outside reader. Exception: .claude/skills/ is tracked — shared skill definitions (e.g. the sbox skill, #42) are reusable engineering reference material, not session state, and are useful to an outside contributor rather than exposing anything private. Everything else under .claude/ (settings.local.json and other local/session state) stays gitignored.
  • Credentials, connection strings, API keys, server configuration with secrets.
  • Build output, bin/, obj/, compiled assets.
  • Large binaries generally. If something big is genuinely needed, discuss it before committing — history is forever.