fix: keep release notes to checked claims

The v2026.10.10 notes said a pack holds every file its emulators open, so
a game never stops on a missing file, and that hashes never come from a
forum list: the gap analysis lists files no pack carries, and in md5
mode a pack keeps the platform's own md5 (verification-modes.md). The
generated text and the README now point to the gap analysis, and
release_notes.py refuses a whats-new text holding a judging or
contradicted phrase.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 08:25:25 +01:00
1 parent fd3733fcaa
commit 556855421a
5 files changed
+62 -6

No files matched your search

+2 -2
View File
@@ -43,7 +43,7 @@ First time? Follow the [first install, step by step](https://abdess.github.io/re
## Download BIOS packs
A pack holds everything its platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **Choose systems**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.
A pack holds its platform's own BIOS list plus the files its emulator cores load. What no pack carries yet is in the [gap analysis](https://abdess.github.io/retrobios/gaps/). Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **Choose systems**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.
A pack over 2 GB comes in several parts (`Pack.part1of2.zip`, `Pack.part2of2.zip`), and every part is needed. Each part is an ordinary ZIP: extract them all into the same folder.
@@ -208,4 +208,4 @@ The scripts and tooling are released under the [MIT License](LICENSE).
The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed.
The reasoning, and where it is weakest, is in [the project's legal position](https://abdess.github.io/retrobios/wiki/legal/).
*Auto-generated on 2026-11-06T05:38:30Z*
*Auto-generated on 2026-11-06T07:24:52Z*
+1 -1
View File
@@ -608,7 +608,7 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json"
"",
"## Download BIOS packs",
"",
"A pack holds everything its platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **" + _installer_words().TUI_SYSTEMS_ROW + "**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.",
f"A pack holds its platform's own BIOS list plus the files its emulator cores load. What no pack carries yet is in the [gap analysis]({SITE_URL}gaps/). Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **" + _installer_words().TUI_SYSTEMS_ROW + "**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.",
"",
"A pack over 2 GB comes in several parts (`Pack.part1of2.zip`,"
" `Pack.part2of2.zip`), and every part is needed. Each part is an"
+24 -2
View File
@@ -7,7 +7,8 @@ issue 81 happened. Install commands, the parts sentence and the table come
from the record and the registry. Each row links the platform's page under
get/ on the site, which outlives the release, and the setups the registry
does not mark release_pack are named with the command. The maintainer writes
what changed, and that text is appended as given.
what changed, and that text is appended as given once it holds none of the
REFUSED phrases.
Usage:
python scripts/release_notes.py --whats-new tmp/whats-new.md --output notes.md
@@ -102,6 +103,20 @@ def _link(name: str, pack: dict) -> str:
return f"[{label}]({release_record.get_page(_owner(name, pack), SITE)})"
# Words the published notes of v2026.10.10 carried by hand: "never from a
# forum list" judged lists other people keep, and "a game never stops on a
# missing file" was a claim the gap analysis contradicts. Text about other
# projects says what changed, not who erred (wiki/release-process.md).
REFUSED = ("their file", "theirs", "of ours", "corrected", "wrong", "drift",
"left out", "never seen", "forum", "never stops", "never fails")
def refused_words(text: str) -> list[str]:
"""The refused phrases the text holds, case ignored."""
lowered = text.lower()
return [phrase for phrase in REFUSED if phrase in lowered]
def render(record: dict, platforms_dir: str, whats_new: str) -> str:
packs = record.get("packs", {})
folders = _extract_folders(record, platforms_dir)
@@ -124,7 +139,8 @@ def render(record: dict, platforms_dir: str, whats_new: str) -> str:
"### Download a pack",
"",
("One complete pack for each platform below: the BIOS list the platform"
" publishes plus every file its emulators open. Each link is a page"
" publishes plus the files its emulators open. Files no pack carries yet"
f" are listed in the [gap analysis]({SITE}gaps/). Each link is a page"
" that keeps its address from one release to the next. Files is what the pack"
" holds once extracted, the count a file manager shows. Extracted size"
" is the space those files take, and the download is smaller."),
@@ -173,6 +189,12 @@ def main() -> int:
if not whats_new.strip():
print(f"Error: {args.whats_new} is empty", file=sys.stderr)
return 1
refused = refused_words(whats_new)
if refused:
print(f"Error: {args.whats_new} says {', '.join(repr(w) for w in refused)}. State what"
" changed and how it was checked, not a verdict on other projects or a"
" promise the gap analysis contradicts", file=sys.stderr)
return 1
write_text_atomic(str(args.output), render(record, args.platforms_dir, whats_new))
print(f"Wrote {args.output} for {record['tag']}, {len(record['packs'])} packs")
return 0
+31
View File
@@ -11,12 +11,14 @@ installer fetches and leaves out the data directories a pack also carries.
from __future__ import annotations
import hashlib
import io
import json
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
from unittest import mock
import yaml
@@ -552,6 +554,35 @@ class TheReleaseNotesReadTheRecord(unittest.TestCase):
def test_the_notes_never_open_on_the_collection(self):
self.assertNotIn("collection", self.render().split("### What changed")[0])
def test_the_notes_claim_only_what_the_gaps_allow(self):
"""v2026.10.10 said a pack holds every file its emulators open, "so a
game never stops on a missing file", and that hashes never come
"from a forum list". The gap analysis lists what no pack carries."""
text = self.render().split("### What changed")[0]
self.assertNotIn("every file its emulators", text)
self.assertIn("https://abdess.github.io/retrobios/gaps/", text)
self.assertEqual(release_notes.refused_words(text), [])
def test_a_judging_whats_new_is_refused(self):
self.assertEqual(
release_notes.refused_words(
"- Every hash comes from the emulator's source, never from a forum list.\n"
"- So a game never stops on a missing file."),
["forum", "never stops"])
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
whats_new = Path(tmp) / "whats-new.md"
whats_new.write_text("- Hashes never from a forum list\n", encoding="utf-8")
record = Path(tmp) / "release.json"
record.write_text(json.dumps(self.RECORD), encoding="utf-8")
argv = ["release_notes.py", "--record", str(record), "--whats-new", str(whats_new),
"--platforms-dir", str(REPO_ROOT / "platforms"),
"--output", str(Path(tmp) / "notes.md")]
with mock.patch.object(sys, "argv", argv), \
mock.patch("sys.stderr", new_callable=io.StringIO) as err:
self.assertEqual(release_notes.main(), 1)
self.assertIn("'forum'", err.getvalue())
self.assertFalse((Path(tmp) / "notes.md").exists())
def test_the_release_process_runs_it(self):
page = (REPO_ROOT / "wiki" / "release-process.md").read_text(encoding="utf-8")
self.assertIn("scripts/release_notes.py", page)
+4 -1
View File
@@ -276,7 +276,10 @@ python scripts/generate_site.py
# 5. Write the notes: install commands, the parts sentence and the pack table
# come from release.json, and what changed since the previous tag is the
# maintainer's own text in tmp/whats-new.md, appended as written.
# maintainer's own text in tmp/whats-new.md, appended as written. The
# script refuses a text that judges another project ("forum list") or
# promises what the gap analysis contradicts ("never stops"): it says
# what changed and how it was checked.
python scripts/release_notes.py --whats-new tmp/whats-new.md --output tmp/notes.md
# Create the release as a DRAFT, upload every asset, and only then publish it.