From 4d0eab1852f6d007d9406b55d34e162f2f5a68a7 Mon Sep 17 00:00:00 2001 From: Jie Date: Sun, 6 Sep 2026 00:27:00 +0800 Subject: [PATCH 1/3] Require one concern per commit, and a bump of its own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The subject is printed beside every file the commit touched, so folding four unrelated changes into one prints a sentence that is a quarter true of each. Releasing 0.0.2-beta.3 put the same line on sixteen paths, CODE_OF_CONDUCT.md and .github/ among them, where it described nothing that had changed. The rule that was missing is about granularity, not wording — the wording rules above were followed. --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad2b864..4a6d745 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,6 +82,23 @@ Every non-trivial commit gets a body, and the body answers **why**: what was wrong before, what breaks if it stays that way, what was measured. A commit whose body only rephrases its subject is a commit with no body. +**One concern per commit, and a version bump is a concern of its own.** The +subject is printed beside every file the commit touched, so a commit that +carries four unrelated changes prints a sentence that is a quarter true of each +of them. Releasing 0.0.2-beta.3 put *Carry the documentation corrections to npm +and the site* on sixteen paths, including `CODE_OF_CONDUCT.md` and +`.github/` — where it said nothing about what had changed. The subject was not +the problem; folding a Code of Conduct contact, a CI job, a hook pattern and a +version bump into one commit was. + +Split by what changed, not by when it shipped: the release ends with a bump +that touches only the files a bump has to touch — `package.json`, +`version.json`, `dist/`, `src/`, `types/`, `tokens.json`, `vocabulary.json`, +`integrity.json`, the badges and pinned paths in the READMEs, `SECURITY.md`, +`CHANGELOG.md`, `demo/`. Everything else is its own commit, made before it. +`tools/release.sh` rejects a release whose final commit reaches outside that +set. + **Keep the subject under about 60 characters.** GitHub's file listing — the view that shows which commit last touched each file — truncates there, and a subject that survives it is the difference between a readable history and a From f34963f37903ecf61d644867b403d917d7fa64f7 Mon Sep 17 00:00:00 2001 From: Jie Date: Sun, 6 Sep 2026 00:28:16 +0800 Subject: [PATCH 2/3] Reject a release commit that reaches outside the bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release commit's subject lands on every file it touched. 0.0.2-beta.3's also carried a Code of Conduct contact and a CI job, so that sentence became the description of CODE_OF_CONDUCT.md and .github/ — neither of which had anything to do with npm or the site. Checked against that commit, this rejects exactly those two paths. --- tools/release.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/release.sh b/tools/release.sh index 611911b..f7eafad 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -73,6 +73,23 @@ if [ -n "$(git status --porcelain)" ]; then exit 1 fi +# The release commit's subject is printed beside every file it touched. When it +# also carries changes a bump did not require, that one sentence becomes the +# description of files it says nothing about: 0.0.2-beta.3 put "Carry the +# documentation corrections to npm and the site" on CODE_OF_CONDUCT.md and +# .github/, neither of which had anything to do with npm or the site. +# +# So the last commit may touch only what a bump has to touch. Anything else +# belongs in its own commit, made before this one — which is also what makes +# each of those files show a sentence about itself. +STRAY=$(git show --name-only --format= HEAD | grep -vE '^(package\.json|version\.json|integrity\.json|tokens\.json|vocabulary\.json|CHANGELOG\.md|SECURITY\.md|README\.md|README\.zh-CN\.md|dist/|src/|types/|demo/|docs/versioning(\.zh-CN)?\.md|docs/projects(\.zh-CN)?\.md)' || true) +if [ -n "$STRAY" ]; then + echo "error: the release commit reaches outside a version bump:" >&2 + printf ' %s\n' $STRAY >&2 + echo " split those into their own commits first — see CONTRIBUTING.md." >&2 + exit 1 +fi + if git rev-parse "$TAG" >/dev/null 2>&1; then echo "error: tag $TAG already exists — bump version.json first." >&2 exit 1 From 622f6b132050fad7d29ad9fa9887d82369ccc17e Mon Sep 17 00:00:00 2001 From: Jie Date: Sun, 6 Sep 2026 01:17:13 +0800 Subject: [PATCH 3/3] Point the footer comment at the test that asserts it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment carried a status line — which integrations were out, which were not — beside the rule it was explaining. The rule is what a reader needs, and the status is asserted by tests/contract.test.mjs rather than by a sentence that goes stale between releases. --- demo/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/index.html b/demo/index.html index fef8ff4..01ebd0b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -336,8 +336,8 @@

Documentation

Ecosystem

    + link to a repository that does not exist yet is a 404. Which name + sits in which list is asserted by tests/contract.test.mjs. -->
  • WordPress · jsray-wpbeta
  • VS Code · jsray-vscodebeta
  • Terminal · jsray-terminalbeta