♻️ Tighten the development/ prose
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.
This commit is contained in:
1 parent
7ea84b66fa
commit
74c39e1346
7 files changed
+341
-409
No files matched your search
+77
-98
@@ -1,164 +1,143 @@
|
||||
# Workflow
|
||||
|
||||
How work moves through the repository: the branching model, the `npm run`
|
||||
script taxonomy, the feedback tiers, and commit messages. The contributor-facing
|
||||
steps are in [CONTRIBUTING.md](../CONTRIBUTING.md); this file records why they
|
||||
are shaped the way they are.
|
||||
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 contributor-facing steps are in
|
||||
[CONTRIBUTING.md § Branching model](../CONTRIBUTING.md#branching-model). Two
|
||||
bits of context behind the model: collaborative review through the Gitea UI is
|
||||
not in place, so Gitea is the lab; and the project will be promoted to GitHub
|
||||
once it is tested and ready for production. What follows is why the front doors
|
||||
exist and what was rejected.
|
||||
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` rather than by
|
||||
prose plus hand-written `git` commands.
|
||||
Work is opened and closed by `create:branch` / `create:finish`, not by prose
|
||||
plus hand-written `git`.
|
||||
|
||||
#### Why
|
||||
|
||||
- The branching model's preconditions were prose. Prose rots silently — a rule
|
||||
nobody checks is a suggestion. A script asserts, then acts, and the branch or
|
||||
merge only happens if the assertions passed.
|
||||
- The type-driven loop only produces trustworthy results 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 a tree that was never eligible.
|
||||
- `create:finish` owns the merge-side preconditions and the post-merge
|
||||
`npm run verify`, so a merge cannot land unverified. The push stays with
|
||||
`create:release` so the merge remains local and reviewable until then.
|
||||
- Every check failure is non-mutating, except the baseline test which runs on
|
||||
`main` after switching there — a red `main` restores the branch you started on
|
||||
rather than stranding you on it. A merge conflict aborts and returns to the
|
||||
feature branch, so a failed finish never leaves a half-merged `main`.
|
||||
- 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`: it is release-shaped and
|
||||
third-party, and would make starting a branch pay for a build and pack it has
|
||||
no use for.
|
||||
- Leaving the merge as reviewer judgment: that judgment still exists, but it now
|
||||
happens when the maintainer reviews the handover _before_ invoking
|
||||
`create:finish`, rather than being encoded in a command that anyone can run
|
||||
from a dirty tree.
|
||||
- `--no-ff` rather than a fast-forward: it keeps each unit of work visible in
|
||||
`git log`.
|
||||
- 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` deliberately does not push, so `main` is ahead of
|
||||
`origin/main` between a merge and the next push or release. `create:branch`
|
||||
requires `main` to match its upstream and will refuse until it is pushed. Push
|
||||
`main` before starting the next branch.
|
||||
- `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).
|
||||
What follows is the design rationale and the `create:` decision.
|
||||
|
||||
One part of the taxonomy is not a prefix rule: some top-level scripts are
|
||||
**bare** (no prefix): the entry points that either run a single tool (`build`,
|
||||
`clean`) or aggregate a `prefix:*` family (`check`, `fix`, `test`, `watch`,
|
||||
`maintain`, `setup`), plus one convenience that composes across tiers: `verify`
|
||||
— composing `check` + `test:unit` into one whole-project correctness gate (it
|
||||
deliberately uses `test:unit` rather than `test` because `check` already runs
|
||||
`check:tsc`, so the type checker runs exactly once). Bare commands are how you
|
||||
invoke a tier; the `prefix:*` scripts are what those tiers are made of.
|
||||
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, and there is no bare `create`
|
||||
`create:` is the prefix for workflow front doors, with no bare `create`
|
||||
aggregator.
|
||||
|
||||
#### Why
|
||||
|
||||
- Both members really do create something: a branch, a release.
|
||||
- The prefix was added to both lists in [CONTRIBUTING.md](../CONTRIBUTING.md) in
|
||||
the same commit as its first members, because a prefix missing from those
|
||||
lists is invisible — which was the `use:` mistake this repo previously
|
||||
carried (since retired into `setup:`).
|
||||
- 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:` — both mean only "do the thing named after them", so every
|
||||
script in the repo would fit under them and the taxonomy collapses.
|
||||
- `git:` — names the tool, not the lifecycle moment, and advertises passthrough
|
||||
- `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 for both, but it needs VCS slang to decode, and a signpost
|
||||
that has to be explained is not one.
|
||||
- `flow:` — overloaded in a library about type-level matching.
|
||||
- The existing families: `check:*` is read-only and aggregated by `check`, so CI
|
||||
would run a command that mutates repo state; `fix:*`'s review surface is a file
|
||||
diff, not a branch; `maintain:*` is advisory and explicitly never a gate.
|
||||
- `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 tier table and the rules for invoking it are in
|
||||
The table and invocation rules are in
|
||||
[CONTRIBUTING.md § Feedback tiers](../CONTRIBUTING.md#feedback-tiers); this
|
||||
section explains why the split is where it is.
|
||||
section explains the split.
|
||||
|
||||
#### Decision (2026-09)
|
||||
|
||||
Split fast, offline, staged-file checks into pre-commit; whole-project test runs
|
||||
into pre-push and `verify`; and slow or network-bound scans into `maintain`.
|
||||
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:*` is a manual tier, not a hook.** The developer starts it on demand
|
||||
(it has to be killed with Ctrl-C) and it runs in a dedicated terminal pane. It
|
||||
sits as the earliest tier in the ladder, catching failures the moment a file is
|
||||
saved — before staging, before commit.
|
||||
- **`check:tsc`, `check:oxlint`, `check:oxfmt`, `check:cspell`** are in
|
||||
pre-commit because they are fast (~0.2–0.5s each), fully offline, and naturally
|
||||
scope to staged files via the `LEFTHOOK_FILES` env var convention. They give
|
||||
instant feedback on what you typed.
|
||||
- **`test` (and the `tsc` it includes) is in pre-push** because it runs the whole
|
||||
test suite across the whole project. The pre-commit `LEFTHOOK_FILES` convention
|
||||
doesn't apply to the test runner, so pre-commit isn't the right home. Pre-push
|
||||
runs after all commits are made but before the push leaves the machine,
|
||||
catching regressions that span multiple commits.
|
||||
- `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
|
||||
|
||||
- Running `maintain:*` in `check` or pre-commit: advisory, whole-project and
|
||||
network-bound scans are not correctness gates and would make the fast tier
|
||||
slow.
|
||||
- Treating a green pre-commit as the definition of done: it only sees staged
|
||||
files, which is why `npm run verify` exists as the one-shot whole-project gate.
|
||||
- A separate `git push` hook for `verify`: it would duplicate the pre-push test
|
||||
tier; `verify` is run by hand because the push is where the whole project is
|
||||
already checked.
|
||||
- `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 from history: `:sparkles: Add watch tier with watch:test child`,
|
||||
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 #...`.
|
||||
body explains what and why, not how; link issues with `Resolves #...`.
|
||||
|
||||
#### Decision (2026-09)
|
||||
|
||||
Gitmoji subjects with imperative mood, wrapped 50/72, rather than Conventional
|
||||
Commits.
|
||||
Gitmoji subjects, imperative mood, wrapped 50/72, not Conventional Commits.
|
||||
|
||||
#### Why
|
||||
|
||||
- The history is gitmoji, not Conventional, and it predates any commit-lint
|
||||
tooling; switching would rewrite the convention for no gain.
|
||||
- The body carries reasoning, which is what a reviewer needs; the subject is a
|
||||
signpost, not a semantic key.
|
||||
- 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 carries no automation value here (see
|
||||
notes, not generated ones, so the prefix has no automation value here (see
|
||||
[publishing.md](./publishing.md)).
|
||||
Reference in new issue
Block a user