From 385aea1683040e25bd24d7920fa49c6cdbc15045 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:25:20 +0000 Subject: [PATCH] File patch notes by what players care about. Co-authored-by: Cursor --- backend/src/patchnotes/feedback.py | 11 +++---- backend/src/patchnotes/summarize.py | 38 ++++++++++++++++++++++-- backend/src/patchnotes/test_feedback.py | 7 +++-- backend/src/patchnotes/test_summarize.py | 37 +++++++++++++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/backend/src/patchnotes/feedback.py b/backend/src/patchnotes/feedback.py index b9acfa2..e316973 100644 --- a/backend/src/patchnotes/feedback.py +++ b/backend/src/patchnotes/feedback.py @@ -49,11 +49,12 @@ - drop a line when staff do not want it posted. A drop needs no body. - When staff only move a line to another section, or only drop it, keep the existing body. Change the body only when they give new wording or ask for a rewrite. - add a line only when staff asked for something that is not already there. At most 3. -- Sections, and staff win when they name one: - - new: a new player-facing thing. - - fixed: a player-facing bug that was fixed. - - adjusted: an existing feature that changed. - - technical: backend work players will not care about. +- The note is structured in four sections. Choose the section from what players care about. Staff win when they name one. + - new: a new thing players care about. It is player facing. + - fixed: a bug players will care about that was fixed. + - adjusted: an existing feature that was adjusted. + - technical: something players will not care about. Backend work about the code, and nothing player facing. +- A player-facing bug fix is fixed. A bug fix or a new change that players will not care about is technical, not fixed or new. - For new, fixed, and adjusted, each body is one short player-facing sentence. A technical line may keep its existing wording. - topic is one of classes, combat, magic, crafting, professions, animals, world, town, dungeons, chat. - highlight is true only for the few lines that belong in the short summary. At most 6. diff --git a/backend/src/patchnotes/summarize.py b/backend/src/patchnotes/summarize.py index 9ac95a4..a7d7690 100644 --- a/backend/src/patchnotes/summarize.py +++ b/backend/src/patchnotes/summarize.py @@ -59,6 +59,27 @@ re.IGNORECASE, ) _NEW_WORDS = re.compile(r"\b(add|added|adds|new)\b", re.IGNORECASE) +# Backend work players will not care about. It stays technical even when the +# commit is written as a feature or a fix. +_NOT_PLAYER_FACING = re.compile( + r"(?i)(" + r"\bloggers?\b|\blogging\b|\bconsole\b|" + r"\bdeprecated\b|\blisteners?\b|" + r"\bworkspace layout\b|\bserver thread\b|" + r"\bdisk reads?\b|\bblock snapshots?\b|" + r"\bmigrat(?:e|ed|ing|ion)\b|" + r"\bmaterial exclusions?\b|" + r"\bfallbacks?\b" + r")" +) +_PLAYER_FACING = re.compile( + r"(?i)\b(" + r"players?|guilds?|recipes?|prices?|costs?|animals?|pets?|" + r"masks?|soups?|roasts?|furniture|discord|shops?|" + r"banners?|icons?|helmets?|skins?|spells?|" + r"characters?|ledgers?|eggs?|dough" + r")\b" +) _SECRET = re.compile( r"(?i)(api[_-]?key|client[_-]?secret|staff[_-]?key|password|private[_-]?key|" @@ -198,14 +219,27 @@ def _split_conventional(subject: str) -> tuple[str, str]: def _section(kind: str, text: str) -> str: - if kind in _KIND_SECTION: - return _KIND_SECTION[kind] + """File a line by what players care about. + + New is a new player-facing thing. Fixed is a player-facing bug that was + fixed. Adjusted is an existing feature that changed. Technical is backend + work players will not care about. + """ + if _NOT_PLAYER_FACING.search(text): + return "technical" + mapped = _KIND_SECTION.get(kind) + if mapped == "technical": + return "technical" + if mapped: + return mapped if _FIX_WORDS.search(text): return "fixed" if _ADJUST_WORDS.search(text): return "adjusted" if _NEW_WORDS.search(text): return "new" + if _PLAYER_FACING.search(text): + return "adjusted" return "technical" diff --git a/backend/src/patchnotes/test_feedback.py b/backend/src/patchnotes/test_feedback.py index 154b554..038f868 100644 --- a/backend/src/patchnotes/test_feedback.py +++ b/backend/src/patchnotes/test_feedback.py @@ -176,8 +176,11 @@ def test_omitted_lines_stay_and_a_move_can_keep_its_body(self) -> None: def test_prompt_returns_changes_only_and_staff_sections_win(self) -> None: self.assertNotIn("Every input id appears once", _SYSTEM) self.assertIn("Leave every other line out", _SYSTEM) - self.assertIn("staff win when they name one", _SYSTEM) - self.assertIn("backend work players will not care about", _SYSTEM) + self.assertIn("Staff win when they name one", _SYSTEM) + self.assertIn("a new thing players care about", _SYSTEM) + self.assertIn("a bug players will care about that was fixed", _SYSTEM) + self.assertIn("an existing feature that was adjusted", _SYSTEM) + self.assertIn("nothing player facing", _SYSTEM) self.assertNotIn("A plugin or internal change uses section technical", _SYSTEM) def test_a_cut_off_reply_says_so(self) -> None: diff --git a/backend/src/patchnotes/test_summarize.py b/backend/src/patchnotes/test_summarize.py index ee428a0..6de89b6 100644 --- a/backend/src/patchnotes/test_summarize.py +++ b/backend/src/patchnotes/test_summarize.py @@ -107,6 +107,43 @@ def test_leaves_out_pull_request_numbers(self) -> None: ) self.assertNotIn("#38", notes.drafts[0].body) + def test_backend_work_stays_technical_even_when_written_as_a_fix(self) -> None: + notes = summarize_push( + { + "ref": "refs/heads/main", + "repository": { + "name": "TLibs", + "full_name": "TF-Minecraft/TLibs", + "default_branch": "main", + }, + "commits": [ + { + "id": "1" * 40, + "message": "fix: use the plugin logger for console messages", + "distinct": True, + }, + { + "id": "2" * 40, + "message": "feat: migrate deprecated level-up listeners", + "distinct": True, + }, + { + "id": "3" * 40, + "message": "Show the company icon under the banner for every guild", + "distinct": True, + }, + ], + } + ) + self.assertEqual( + [(draft.section, draft.body) for draft in notes.drafts], + [ + ("technical", "TLibs: use the plugin logger for console messages"), + ("technical", "TLibs: migrate deprecated level-up listeners"), + ("adjusted", "TLibs: Show the company icon under the banner for every guild"), + ], + ) + def test_strips_coordinates_and_keeps_the_fix(self) -> None: notes = summarize_push(_push("fix: Fixed a chest at 1204, 64, -880")) self.assertEqual(len(notes.drafts), 1)