Goal: keep dev and main strictly linear with rebase-only PR merges, publish production by fast-forwarding main to dev, and publish immutable releases from annotated vX.Y.Z tags on main.
- Set up required settings on GitHub
- Publish to dev site: merge PRs into
dev(rebase-only) → pushing todevtriggers deployment todev.lex-0.org. - Publish to production site: fast-forward
maintodev→ pushing tomaintriggers deployment tolex-0.org. - Publish an immutable release: create an annotated tag
vX.Y.Zonmainand push the tag → GitHub Actions publishes togh-pages/releases/vX.Y.Z/and updates the releases index ongh-pages.
Always create feature branches from dev.
- CLI:
git checkout dev && git pull && git checkout -b feature/xyz - GitHub Desktop: Switch to
dev→ Branch > New Branch (base ondev)
- CLI:
gh pr create --base dev - GitHub Desktop: Create PR (will default to targeting
main), then change manually online.
Work on your feature and commit freely; when you’re ready to open a PR, rebase onto dev only if dev is ahead of your feature. If dev hasn’t moved past your branch point, just open the PR without rebasing.
- CLI:
git fetch && git rebase origin/dev→ push withgit push --force-with-lease - GitHub Desktop: Branch > Rebase Current Branch… → select
dev, then Push (will force-push the rebased branch)
- GitHub PR: choose “Rebase and merge” (rebase-only). This keeps
devlinear and preserves per-commit history. See Required GitHub.com settings below. - GitHub Desktop: use “Create Pull Request” to open on GitHub; complete with a non-merge-commit option. Avoid local merges that create merge commits.
- CLI (preferred):
git checkout main && git fetch && git merge --ff-only origin/dev && git push(advancesmaintodevwith no merge commit). - GitHub UI/Desktop: GitHub PRs don’t offer a true fast-forward merge; “rebase”/“squash” create new commits. If you want
mainto exactly matchdevwith no new commits, you MUST use the CLI fast-forward above.
Releases are immutable snapshots published under lex-0.org/releases/vX.Y.Z/ (GitHub Pages, behind a Vercel rewrite). The release workflow is tag-driven and requires an annotated tag on main.
- GitHub Pages is enabled and configured to serve from the
gh-pagesbranch. - The
build-siteworkflow exists (.github/workflows/site-build.yml) and is green on pushes. - Vercel projects are configured to deploy
vercel-mainandvercel-devbranches.
Run release doctor before cutting a release:
npm run release:doctor -- --tag vX.Y.Z
It checks branch topology, rulesets, workflow wiring, required secrets, tag
collisions, tag vs odd/lex-0.odd edition alignment, and release metadata
readiness (date-released on origin/dev).
Use repo-local Node scripts:
- Prepare release metadata PR on
dev:npm run release:prepare -- --tag vX.Y.Z
- End-to-end release (prepare -> FF
devtomain-> triggerrelease-helper):npm run release:cut -- --tag vX.Y.Z
Both support --dry-run. By default they run in non-interactive auto-confirm mode;
use --interactive to require prompts.
Legacy fish helpers can still be used, but the npm scripts are the canonical path.
Use the GitHub Actions release-helper workflow (.github/workflows/release-helper.yml, manual trigger). It is gated to ttasovac and:
- validates that
mainalready containsdev(FF promotion must already be done) - validates that
CITATION.cffonmainalready includesdate-released - creates an annotated tag
vX.Y.Z
Then the normal tag build publishes to gh-pages/releases/vX.Y.Z/.
-
Prepare release citation metadata via a PR to
dev(becausedevis PR-only):git checkout dev && git pull --ff-only origin devgit checkout -b chore/release-prep-vX.Y.Znode scripts/update-citation-metadata.mjs --commit "$(git rev-parse origin/dev)" --date "$(date -u +%F)" --date-released "$(date -u +%F)"git add CITATION.cff && git commit -m "chore: prepare citation metadata for vX.Y.Z"git push -u origin chore/release-prep-vX.Y.Zgh pr create --base dev --head chore/release-prep-vX.Y.Z --title "chore: prepare citation metadata for vX.Y.Z" --body ""gh pr merge --rebase --auto
-
Wait for GitHub Actions →
build-siteondevto finish successfully. -
Fast-forward
maintodev(see above.) -
Wait for GitHub Actions →
build-siteonmainto finish successfully (this deployslex-0.org). -
Create an annotated tag on
mainand push it:git checkout maingit pull --ff-only origin maingit tag -a vX.Y.Z -m "Release vX.Y.Z"git push origin vX.Y.Z
-
Monitor GitHub Actions →
build-site→tag_releasejob:- Publishes
build/htmltogh-pages/releases/vX.Y.Z/ - Regenerates
gh-pages/releases/index.html
- Publishes
-
Verify:
https://lex-0.org/releases/vX.Y.Z/loads- Assets resolve (no
github.ioURLs)
Notes:
- Tags must be annotated. The workflow rejects lightweight tags.
- Re-using an existing tag (or attempting to publish a release folder that already exists) is blocked by CI.
Note: GitHub Desktop is fine for day-to-day work, but the release tag must be an annotated git tag, so you must use a terminal for this.
These settings enforce a rebase-only workflow on dev, while still allowing admin to fast-forward main to dev from the CLI.
Repo settings → Secrets and variables → Actions
CITATION_BOT_TOKENis optional now (used only if you run the legacy/manualcitation-metadataworkflow directly).
Repo settings → Pull Requests
- Enable: “Allow rebase merging”
- Disable: “Allow merge commits”
- Disable: “Allow squash merging” (rebase-only)
- Enable: “Allow auto-merge” (required for automated release-prep PR merges)
- Do not set “Always suggest updating pull request branches” (the “Update branch” flow is not compatible with rebase-only workflows)
Repo settings → Rules → Rulesets
Use two rulesets, because dev and main have different constraints.
- Ruleset for
dev(PR-only):- Target branches (fnmatch pattern):
dev - Enable: “Require a pull request before merging”
- Enable: “Require linear history”
- Enable: “Block force pushes” and “Block deletions”
- Required status checks:
check_citationandpr(build-site PR job)
- Target branches (fnmatch pattern):
- Ruleset for
main(FF-only by admin):- Target branches (fnmatch pattern):
main - Enable: “Require linear history”
- Enable: “Block force pushes” and “Block deletions”
- Do not require pull requests (FF promotion is a direct push)
- Do not require status checks (checks run on
devbefore FF promotion)
- Target branches (fnmatch pattern):
Bypass list
- Keep the bypass list empty for the
devruleset. - For
main, prefer “restrict who can push” over bypass. If you can’t restrict pushes, add only yourself to the bypass list and keep “Block force pushes” enabled.
In a nutshell: never rebase dev or main. Only rebase your own feature branches, then force-push them with --force-with-lease after rebasing.
For deployment architecture details (Vercel, GitHub Pages, release archive), see deployment.md.