Files
tiny-pattern-ts/development/workflow.md
T
tmu f6f820a648 📝 Require a changelog note before finishing a branch
A merged branch must carry a final commit adding a short summary of the
work under [Unreleased] in CHANGELOG.md. create:release derives the bump
heuristic from that body, so notes have to exist before release day;
rationale in development/workflow.md.
2026-09-15 22:08:08 +00:00

174 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## Changelog notes
The rule is in
[CONTRIBUTING.md § Rules the tools don't enforce](../CONTRIBUTING.md#rules-the-tools-dont-enforce).
#### Decision (2026-09)
A merged branch carries its own summary under `[Unreleased]` in
[CHANGELOG.md](../CHANGELOG.md), added before `create:finish`;
`create:release` graduates it into the tagged section (see
[publishing.md](./publishing.md)).
#### Why
- `create:release` derives the bump heuristic from the `[Unreleased]` body, so
the notes must exist before release day.
- The contributor has fresh context; at release day the intent of a branch is
only its diff.
- Gitmoji subjects are signposts, not semantic keys, so notes cannot be derived
from the history.
#### Rejected
- Generating notes from subjects at release time: subjects carry no parseable
type/scope (see § Commit messages).
- The maintainer writing one summary during `create:release`: reconstruction
after the fact.
- Enforcing it in `create:finish`: the front doors assert git state, not
content — and _notable_ is exactly the judgment a tool cannot make.
## 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)).