Same decisions, rationale, rejected alternatives and known issues, said with less padding: ~5,530 -> ~4,730 words (-15%). Every fact from the first draft is kept; only the wording, duplicated lead-ins and restated context are cut.
5.8 KiB
Workflow
How work moves through the repository. The rules are in CONTRIBUTING.md; this file records why they are shaped the way they are.
Branching model
The model is GitHub Flow (single-developer); the steps are in CONTRIBUTING.md § Branching model. Context behind it: Gitea has no collaborative review UI in use, so it is the lab, and the project moves to GitHub once it is tested and ready.
Decision (2026-09)
Work is opened and closed by create:branch / create:finish, not by prose
plus hand-written git.
Why
- The preconditions were prose, and prose rots: a rule nobody checks is a suggestion. A script asserts, then acts, so the branch or merge only exists if the assertions passed.
- Type-driven work is only trustworthy if the baseline was green before the
first edit. Cheap checks run first and
npm run testlast, so the expensive gate is not paid on an ineligible tree. - The merge half owns the post-merge
npm run verify, so a merge cannot land unverified. The push stays withcreate:releaseso the merge is reviewed locally first. - Every failure is non-mutating except the baseline test, which runs on
mainafter switching there: a redmainrestores the branch you started on, and a merge conflict aborts back to the feature branch rather than stranding a half-mergedmain.
Rejected
- Hand-written
git switch -c/git merge: same rules, no enforcement. - Reusing
pubv's preflight forcreate:branch: release-shaped, third-party, and it would pay for a build and pack a new branch has no use for. - Leaving the merge to reviewer judgment: that judgment moved earlier, to the
handover review before
create:finish, rather than living in a command anyone can run from a dirty tree. - Fast-forward instead of
--no-ff:--no-ffkeeps each unit of work visible ingit log.
Known issue
create:finishdoes not push, somainis ahead oforigin/mainbetween a merge and the next push.create:branchrequiresmainto match its upstream and refuses until it is pushed; pushmainbefore starting the next branch.
Script prefix convention
The prefix taxonomy is the rule, and it lives in
CONTRIBUTING.md § Script prefix convention.
The design behind it: bare scripts are the tier entry points — a single tool
(build, clean) or an aggregator of a prefix:* family (check, fix,
test, watch, maintain, setup) — while verify composes check +
test:unit into the whole-project gate (it uses test:unit, not test,
because check already runs check:tsc).
Decision (2026-09)
create: is the prefix for workflow front doors, with no bare create
aggregator.
Why
- Both members create something real: a branch, a release.
- It joined both lists in CONTRIBUTING.md alongside its
first members, so it could not go invisible the way the retired
use:prefix did. publish:*already set the precedent for a prefix without an aggregator.
Rejected
run:/perform:: they mean only "do the named thing", so every script fits and the taxonomy collapses.git:: names the tool, not the lifecycle moment, and implies passthrough aliases.start:: describes the branch half, not the release.cut:: idiomatic but needs VCS slang to decode.flow:: overloaded in a type-level matching library.- The existing families:
check:*is read-only (CI would run a state-mutating command),fix:*reviews as a diff not a branch,maintain:*is advisory and never a gate.
Feedback tiers
The table and invocation rules are in CONTRIBUTING.md § Feedback tiers; this section explains the split.
Decision (2026-09)
Fast, offline, staged-file checks sit in pre-commit; whole-project test runs in
pre-push and verify; slow or network-bound scans under maintain.
Why
watch:*runs until killed, in its own pane, so it is the earliest tier, firing on save before staging or commit.check:tsc/check:oxlint/check:oxfmt/check:cspellare fast (~0.2–0.5s each), offline, and scope to staged files viaLEFTHOOK_FILES, so pre-commit gives instant feedback on what you typed.test(and itstsc) runs the whole suite over the whole project, and the staged-file convention does not apply to the test runner, so it belongs in pre-push, after the commits exist but before the push leaves the machine.
Rejected
maintain:*incheckor pre-commit: advisory, whole-project and network-bound scans are not correctness gates and would slow the fast tier.- Treating a green pre-commit as the definition of done: it sees only staged
files, hence
npm run verify. - A separate
git pushhook forverify: the pre-push test tier already covers it.
Commit messages
The convention is in
CONTRIBUTING.md § Commit messages.
Examples: :sparkles: Add watch tier with watch:test child,
:recycle: Move type-aware config to .oxlintrc.json; use source-level disable directives, :memo: Restore unique maintainer content as CONTRIBUTING.md. The
body explains what and why, not how; link issues with Resolves #....
Decision (2026-09)
Gitmoji subjects, imperative mood, wrapped 50/72, not Conventional Commits.
Why
- The history is gitmoji and predates any commit-lint tooling; switching would rewrite the convention for no gain.
- The body carries the reasoning a reviewer needs; the subject is a signpost, not a semantic key.
Rejected
- Conventional Commits: the release flow uses hand-written Keep-a-Changelog notes, not generated ones, so the prefix has no automation value here (see publishing.md).