Skip to content

Development process ​

How a change gets from an idea to master: the branches it lives on, the shape its commits must have, and the one gate every change passes. What happens after master, the deploy and the version, is in Deploy Strategy and Release Process.

The process is sized for what the project is: two authors, one deployed service, one production version at a time. Every rule below is there because it pays for itself at that size, and the heavier conventions a larger team would reach for are left out on purpose, with the reason.

Branches ​

BranchLivesIs for
masterforeverProduction. Every push deploys it and may release it.
devforeverIntegration and QA. Every push deploys it to the preview environment.
<type>/<topic>daysOne change. <type> is the Conventional Commits type it carries: feat/, fix/, docs/, refactor/, ci/, chore/.
renovate/*hoursOne dependency update, opened by Renovate.

A topic branch is merged into dev when it needs to be seen next to the other author's work on a deployed environment first, and straight into master when it does not. dev reaches master through a pull request like any other branch.

Why not GitFlow. Its release/* and hotfix/* branches exist to prepare and patch versions that are shipped and supported side by side. This project ships one version, the one running in production, and never patches an older one, so those branches would be ceremony with nothing to protect. What GitFlow gets right, an integration branch that is exercised before production, is kept, and it is dev.

Why not trunk only. With two people changing the same screens in the same week, a shared environment that is not production is where one author's change meets the other's before a player does. That is the one job dev has, and it is why it deploys.

Commits ​

Every commit follows Conventional Commits: feat(frontend): reveal the podium while the season is still running. The type is not decoration. It decides the next version (Release Process), it names the branch, and it sorts the release notes.

It is enforced where a commit is written: a commit-msg hook, installed by the Gradle build itself (settings.gradle.kts), rejects a subject that does not parse. There is no step to remember, a first ./gradlew run installs it.

The hook has one blind spot, and it has been hit. A squash merge on GitHub takes its subject from the merge dialog, where no local hook runs. That is how Feat: Implement the team dashboard reached master on 2026-03-23, with a type the release tooling does not recognise. The pull request template asks for a Conventional Commits title for that reason, and the one who merges is the check.

One commit is one change. A pull request whose history is a series of attempts is squashed; one whose commits are each a finished change is rebased, and the commits land as they are.

The gate: pull requests into master ​

master is guarded by a repository ruleset, not by convention:

RuleWhat it stops
A pull request is requiredA change nobody but its author has seen
ci-cd / success must pass, on a branch up to date with masterA change that is green only against an older master
Merge by squash or rebase only; linear historyA merge commit, see below
Signed commitsA commit whose author cannot be verified
No force push, no deletionRewriting what production was built from

ci-cd / success is the aggregate of check.yml: format, lint, typecheck, audit, and every test suite on both persistence targets (Continuous delivery).

The ruleset asks for no approval, and that is deliberate at this size. A mandatory second review would stop whichever author is working while the other is not, and the checks carry the part of review a machine can do. Review by the other author happens on the pull request when the change calls for it, and the tooling does not force it: of the last 25 pull requests other than Renovate's, 10 carry a formal review and 23 a discussion.

Why no merge commits. master's history is the input to the release: the version is computed from its commits and the notes are its commits, grouped. A merge commit adds an entry that is not a change, and a branch's commits interleaved with master's make a release's contents depend on commit dates rather than on what was merged. A linear master is also one git bisect can walk without being told which parent to follow.

Pull requests from forks run check.yml like any other: the workflow names no secret, and the jobs that need one either skip a fork or run only on a deploying branch. See the dispatcher in .github/workflows/ci-cd.yml.

Dependencies ​

Every dependency is declared: package-lock.json locks each npm project, and the JVM module's direct dependencies are pinned in Gradle version catalogs. Renovate opens a renovate/* branch per update and merges it once ci-cd / success passes. The shared preset it extends holds an npm release back three days first, npm's window for retracting one. Merging updates nobody watched is acceptable for one reason only: the gate is the whole test suite on both persistence targets, and an update that breaks an interface breaks a test.

Built from the repository's own documentation. Source on GitHub.