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.
144 lines
5.8 KiB
Markdown
144 lines
5.8 KiB
Markdown
# Workflow
|
||
|
||
How work moves through the repository. The rules are in
|
||
[CONTRIBUTING.md](../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](../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 test` last, 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 with `create:release` so the merge is reviewed
|
||
locally first.
|
||
- Every failure is non-mutating except the baseline test, which runs on `main`
|
||
after switching there: a red `main` restores the branch you started on, and a
|
||
merge conflict aborts back to the feature branch rather than stranding a
|
||
half-merged `main`.
|
||
|
||
#### Rejected
|
||
|
||
- Hand-written `git switch -c` / `git merge`: same rules, no enforcement.
|
||
- Reusing `pubv`'s preflight for `create: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-ff` keeps each unit of work visible
|
||
in `git log`.
|
||
|
||
#### Known issue
|
||
|
||
- `create:finish` does not push, so `main` is ahead of `origin/main` between a
|
||
merge and the next push. `create:branch` requires `main` to match its upstream
|
||
and refuses until it is pushed; push `main` before starting the next branch.
|
||
|
||
## Script prefix convention
|
||
|
||
The prefix taxonomy is the rule, and it lives in
|
||
[CONTRIBUTING.md § Script prefix convention](../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](../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](../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:cspell` are fast
|
||
(~0.2–0.5s each), offline, and scope to staged files via `LEFTHOOK_FILES`, so
|
||
pre-commit gives instant feedback on what you typed.
|
||
- `test` (and its `tsc`) 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:*` in `check` or 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 push` hook for `verify`: the pre-push test tier already covers
|
||
it.
|
||
|
||
## Commit messages
|
||
|
||
The convention is in
|
||
[CONTRIBUTING.md § Commit messages](../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](./publishing.md)).
|