How work moves through this repository. Everything here is meant to be true of the repository as it stands — if the history contradicts a rule, fix the rule in the same change that taught you. Decided on 27 September 2026, when vanilla and the MERL-T line became one product and a second developer joined; the history at the end says what it replaced and why.
| Ref | What it is | Lifetime |
|---|---|---|
main |
The released version: what testers get. Changes only through pull requests from develop (releases) and hotfix/…. |
Permanent |
develop |
Where work integrates. GitHub's default branch. | Permanent |
vX.Y.Z tags |
One per release, on main. v1.7.8 is the last release of the old line, v1.8.0 the first of the unified repository. |
Permanent |
feat/, fix/, refactor/, chore/, docs/ |
One piece of work each, from develop, back into develop by pull request, deleted on merge. |
Days |
hotfix/… |
An urgent fix to the released version, from main, back into main. |
Hours |
preview/… |
Unreleased work run on the deployment host, only when the owner asks for it (see "Deploy"). Never merged. | Until the next release |
No other long-lived branch. Experimental work sits behind flags (MERL-T's
MERLT_ENABLED and its per-area flags). A research branch is opened only if
unavoidable, and it never holds a security fix.
git switch develop && git pull --ff-only, thengit switch -c feat/thing.- Work, and commit often with Conventional Commits (
feat(scope): …). - Before pushing, run the suites of the areas you touched (root
CLAUDE.md, "Commands"). A branch rebases ondevelopwhendevelopmoves; it does not mergedevelopin. git push -u origin feat/thing, then a pull request intodevelop.- CI must be green: GitHub refuses the merge until every CI job has passed. On the paths in "Review" below, GitHub asks the other developer for a review; it does not wait for it.
- Merge it yourself with a merge commit titled
merge: feat/thing — what it changes. The branch is deleted automatically. The title is what the changelog is written from, so it says what changes for the reader, not how.
Nothing is pushed to develop or main directly, nobody force-pushes them,
nobody deletes them: GitHub's rulesets refuse it, and the shared Claude Code
hook refuses it first.
Each developer manages their own code: a pull request is reviewed by the other when they can, and it never waits for that review. The owners decided so on 4 October 2026.
.github/CODEOWNERSasks for the other developer's review on the paths where one person's mistake costs everyone: authentication and tokens, the Prisma schema and migrations, the licences,.github/,infra/and the data scripts, the shared Claude Code rules, and the Normattiva text extraction (its output is the offset space of every stored highlight and note). The ruleset does not require that review: the author merges at green CI.- A review that arrives after the merge is answered by a new pull request.
- The licence, copyright assignment and contribution terms are decided by
both owners together. A pull request that changes
LICENSE,NOTICE, the SPDX headers or the contribution terms is merged only with both owners' agreement written on it.
Both developers' sessions work in parallel, so two things are reserved before anyone touches them, in the owners' shared coordination register:
- a migration: its timestamp (Prisma's
YYYYMMDDHHMMSS_name) and the tables it changes, so two branches never write the same timestamp or the same table; - a shared file that several areas edit — the root
CLAUDE.md,.claude/settings.json,.github/,infra/,start.sh— for the time a branch holds it.
The reservation is released when the branch merges or is abandoned. A conflict found in a pull request is settled between the two developers, not by whoever merges first.
X.Y.Z, read from version.txt:
- Y, the minor number, goes up with each batch of features: roughly every one to two weeks (1.8, 1.9…).
- Z, the patch number, goes up with a hotfix (1.8.1).
- X moves only by the owners' decision.
Each release has a section in CHANGELOG.md, in English, written from the
merge titles (sh scripts/release-notes.sh vPREVIOUS origin/develop lists
them, after a git fetch),
and a short note in Italian for the people who use VisuaLex: what changed in
the parts they use, by area, without technical terms, never naming a doctrine
source («una fonte esterna di dottrina»). The note is a file of the web app,
apps/web/src/content/novita/X.Y.Z.md (a bold line per area, - per item,
no # heading): the window «Novità» shows it after an update, and the GitHub
release carries the same text.
A release is cut when the owners say so. The checklist, in order:
- Suites.
developis green in CI, and every suite in the rootCLAUDE.md("Commands") passes on a checkout of it — MERL-T's in CI or against a disposable database. - Browser. A pass on
http://localhost:5173through the main flows: login; search a norm, read it, change its date; open a decision; add both to a dossier and export its PDF; the find window; a study card; «Collega un'app AI». Anything broken is fixed ondevelopfirst. - Changelog. On
chore/release-X.Y.Zfromdevelop: bring[Unreleased]inCHANGELOG.mdup to date fromrelease-notes.sh(pull requests do not edit it, so they never conflict on it), rename it to[X.Y.Z] - YYYY-MM-DD, and open a new empty[Unreleased]above it; setversion.txttoX.Y.Z; write the Italian note inapps/web/src/content/novita/X.Y.Z.md, in the same pull request (the web suite fails on aversion.txtwith no note). Pull request intodevelop, merge. The reviews' store: ifSCHEMA_VERSIONinservices/visualex/visualex_api/services/massimario/store.pychanged since the last release, the host needs a store of the new schema before the deploy (build it there, or copy one built from the archive:services/visualex/CLAUDE.md); until then the panel says the reviews are not available. A one-volume build does not read an older schema's store, and deletes it. - Pull request
develop → main, titledmerge: develop — release X.Y.Z. The check "Release source" accepts onlydevelopandhotfix/…as the source of a pull request intomain. Merge it with a merge commit. - Tag, on
main:git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin vX.Y.Z, then the GitHub release from the tag, with the changelog section and the Italian note (apps/web/src/content/novita/X.Y.Z.md, then the changelog section):gh release create vX.Y.Z --title "VisuaLex X.Y.Z" --notes-file <note>. - Deploy (below), and record it in the coordination register.
The deployment host runs the latest release: ./start.sh --prod --branch main. A preview of unreleased work runs there only when the owner asks for
it explicitly, recorded in the coordination register: a preview/… branch
deployed with ./start.sh --prod --no-pull --allow-branch, back to the latest
release when the next one is cut. The preview branch is then deleted. A
preview carries no migration that develop lacks: the deploy refuses to move
the host to a version without a migration it already applied, so such a
preview could not go back to the release.
git switch -c hotfix/thing origin/main, fix, raise the patch number inversion.txt, add its section toCHANGELOG.mdand its Italian note inapps/web/src/content/novita/X.Y.Z.md; pull request intomain.- Merge it, then tag and release on
mainas in "Release", steps 5 and 6. - Right after, a pull request
main → develop.
Until the second one lands, the check "main inside develop" is red on
develop: it fails whenever main holds a change develop lacks (release
merges do not count). A forgotten hotfix becomes a red check, not a fix
stranded for weeks.
- Claude Code web sessions push
claude/…branches: within days, open them as a pull request intodevelop, or delete them. - Desktop sessions leave worktrees under
.claude/worktrees/: remove them once their work has landed, after checking for untracked files. - Remote branches are deleted by GitHub when their pull request merges. A branch whose pull request was closed without merging is deleted by its author.
- Once a month:
git fetch --prune,git branch -r | grep origin/claude/,git branch --merged develop,git worktree list.
Until 26 September 2026 there were two lines: main ("vanilla"), deployed to a
production server and tagged at each deploy, and visualex-merlt-main, the
MERL-T experiment, which absorbed main one way, by tag. That rule came from
two incidents: in June 2026, 32 vanilla commits (four of them security fixes)
sat on the experiment for ten weeks; between June and September the experiment
fell 177 commits behind. On 26 September the owners unified the two lines, the
production server was decommissioned, and a second developer joined. The
experiment's last state is the tag archive/visualex-merlt-main; the old
deploy is described in docs/archive/deployment-lightsail.md.
A dev branch had been rejected in September 2026 for three reasons, each
answered by the new situation. It had died once as an echo of main, with one
developer: now there are two, and develop → main is a real step, the release.
Three long-lived branches meant two synchronisation directions: the
experiment's branch is gone, and the one way back — a hotfix — is checked
automatically. A dev nobody deploys is a queue: still true while there is no
test environment, so release small and often.