Every `ts`-tagged fence in README.md / CONTRIBUTING.md now becomes an executed `node:test` case in a gitignored generated file, so a documented example cannot drift from the API. The generator hoists and merges the leading imports, rewrites the library specifier to the `#test-tiny-pattern-ts` alias, and rejects a fence with no describing paragraph or one that re-imports a prelude module. CI regenerates via `pretest:ci`; `create:doc-tests` runs the generator, formats, then typechecks the output against a scoped tsconfig that relaxes `noUnusedLocals`. c8's default excludes already omit the generated `*.test.ts`, so `test:ci` is unchanged.
7.1 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;mainis therefore routinely ahead of its upstream between a merge and the release that ships it.create:branchrequires only thatmainis not behind — matchingcreate:finish, which tolerates the local merge and fast-forwards over a remote one — rather than an exact match. - 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. - Pushing from
create:finishto keepmainlevel with its upstream: it would trade the local review the push waits for for a network side effect, and a failed push would leave the merge landed but unpublished.
Changelog notes
The rule is in CONTRIBUTING.md § Rules the tools don't enforce.
Decision (2026-09)
A merged branch carries its own summary under [Unreleased] in
CHANGELOG.md, added before create:finish;
create:release graduates it into the tagged section (see
publishing.md).
Why
create:releasederives 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.
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
- Every member creates something real: a branch, a release, the compiled doc-tests.
- 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).