Files
tmu 30d97a9209 ✨ Compile doc code fences into tests
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.
2026-09-24 11:53:29 +00:00

7.1 KiB
Raw Permalink Blame History

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 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; main is therefore routinely ahead of its upstream between a merge and the release that ships it. create:branch requires only that main is not behind — matching create: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 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.
  • Pushing from create:finish to keep main level 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: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. 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: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. 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).