From 82d8736ba617060c4b089d433a41bcabaeb9d413 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 22 Sep 2026 10:01:57 +0200 Subject: [PATCH 1/2] ci: stop counting the appended PR number against the subject limit CI checks a PR title before the squash merge appends " (#106)", so a title that passed could land over 72 characters and then fail the release gate. Two commits since the last tag did. The limit now applies to the subject without the number GitHub adds. --- tests/unit/framework/test_commit_lint.py | 7 +++++++ tools/commit-lint | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/unit/framework/test_commit_lint.py b/tests/unit/framework/test_commit_lint.py index adaef6b4..23c64207 100644 --- a/tests/unit/framework/test_commit_lint.py +++ b/tests/unit/framework/test_commit_lint.py @@ -81,6 +81,13 @@ def test_too_long_to_read_in_a_list(self): subject = "fix(core): " + "x" * 80 assert any("limit is 72" in p for p in commit_lint.check(subject)) + def test_the_pr_number_github_appends_is_not_counted(self): + """CI checks the PR title before a squash merge appends " (#106)", + so counting the suffix failed subjects that passed CI.""" + subject = "test(archivist): pin the note in the demo-rig todo capture scenario" + assert len(subject) <= commit_lint.SUBJECT_LIMIT + assert commit_lint.check(f"{subject} (#106)") == [] + def test_written_as_a_title(self): problems = commit_lint.check("feat(photos): Stop the upload stalling") assert any("capital" in p for p in problems) diff --git a/tools/commit-lint b/tools/commit-lint index 9440c758..d7428751 100755 --- a/tools/commit-lint +++ b/tools/commit-lint @@ -126,9 +126,12 @@ def check(header: str) -> list[str]: f"'{scope}' is not a scope. Use one of: {', '.join(sorted(SCOPES))}" " (or no scope at all)") - if len(header) > SUBJECT_LIMIT: + # A squash merge appends " (#106)" after CI has checked the title, so + # the number GitHub adds is not the author's to budget for. + length = len(header) - (len(f" (#{match['pr']})") if match["pr"] else 0) + if length > SUBJECT_LIMIT: problems.append( - f"{len(header)} characters, limit is {SUBJECT_LIMIT}") + f"{length} characters, limit is {SUBJECT_LIMIT}") if subject[:1].isupper(): problems.append("starts with a capital; the subject is a sentence " From 6d4091a012c99bd2186af67a9595c6f4866acbd7 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 22 Sep 2026 10:01:58 +0200 Subject: [PATCH 2/2] ci: accept diary, capture and voice as commit scopes Eight commits since the last tag use these scopes, and the release gate rejected every one. Each names a component with an owner, like `curator` and `wiki` already do: the diary renders as the Memory Stacklet, capture as the Archivist Bot, voice as the AI Stacklet. The scope table in the engineer guide lists them too. --- docs/agent/dev.md | 6 ++++-- tests/unit/framework/test_commit_lint.py | 3 +++ tools/commit-lint | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/agent/dev.md b/docs/agent/dev.md index 8517b257..3343ff31 100644 --- a/docs/agent/dev.md +++ b/docs/agent/dev.md @@ -306,7 +306,9 @@ The scope is what a reader recognises, never a file or a module: | `mail` | Mail Bot | | `scribe` | Scribe Bot | | `stacker` | Stacker Bot | -| `curator`, `wiki` | Memory Stacklet | +| `curator`, `wiki`, `diary` | Memory Stacklet | +| `capture` | Archivist Bot | +| `voice` | AI Stacklet | | `stack`, `cli`, `doctor`, `update`, `install` | stack CLI | | `web` | web fetch | | *(none)* | General | @@ -317,7 +319,7 @@ A stacklet renders as " Stacklet" and a bot as " Bot": one release line saying "Archivist Bot" and the next saying "Docs Stacklet" tells you which one changed without opening anything. -Anything that is neither, a component like the wiki or the curator, renders +Anything that is neither, a component like the wiki, the curator or capture, renders as the stacklet that owns it. `docs` is both a type and a scope and they mean different things. Type `docs` diff --git a/tests/unit/framework/test_commit_lint.py b/tests/unit/framework/test_commit_lint.py index 23c64207..39b73923 100644 --- a/tests/unit/framework/test_commit_lint.py +++ b/tests/unit/framework/test_commit_lint.py @@ -41,6 +41,9 @@ class TestSubjectsThatPass: "feat(docs)!: move Paperless to 3.0.4", "chore(release): v0.3.0-beta.4", "fix(ai): install the local AI engine on current Homebrew (#72)", + "fix(diary): front-page intro addresses the family directly", + "feat(capture): file a link card when a page cannot be read (#96)", + "feat(voice): treat a voice message like any other message (#85)", ]) def test_real_subjects_from_the_history(self, subject): assert commit_lint.check(subject) == [] diff --git a/tools/commit-lint b/tools/commit-lint index d7428751..88cb4709 100755 --- a/tools/commit-lint +++ b/tools/commit-lint @@ -64,9 +64,12 @@ SCOPES = { "mail": "Mail Bot", "scribe": "Scribe Bot", "stacker": "Stacker Bot", - # Components that are neither: the stacklet that owns them. + # Components that are neither: the stacklet or bot that owns them. "curator": "Memory Stacklet", "wiki": "Memory Stacklet", + "diary": "Memory Stacklet", + "capture": "Archivist Bot", + "voice": "AI Stacklet", # The framework itself. "stack": "stack CLI", "cli": "stack CLI",