@@ -75,12 +75,16 @@ optional-dependencies.dev = [
7575 " sphinx-pyproject==0.3.0" ,
7676 " sphinx-substitution-extensions==2026.1.12" ,
7777 " sphinxcontrib-spelling==8.0.2" ,
78+ # ``sphinxcontrib-towncrier`` renders unreleased news fragments
79+ # into docs/source/unreleased.rst during Sphinx builds.
80+ " sphinxcontrib-towncrier==0.5.0a0" ,
7881 # Listed explicitly (despite being transitive via vws-python-mock) so that
7982 # [tool.uv.sources] can redirect to the CPU-only PyTorch index.
8083 # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation
8184 " strict-kwargs==2026.5.19.post3" ,
8285 " torch>=2.5.1" ,
8386 " torchvision>=0.20.1" ,
87+ " towncrier==25.8.0" ,
8488 " ty==0.0.38" ,
8589 " types-pyyaml==6.0.12.20260518" ,
8690 " vulture==2.16" ,
@@ -277,6 +281,8 @@ ignore = [
277281 " *.enc" ,
278282 " .pre-commit-config.yaml" ,
279283 " CHANGELOG.rst" ,
284+ " newsfragments" ,
285+ " newsfragments/**" ,
280286 " CODE_OF_CONDUCT.rst" ,
281287 " CONTRIBUTING.rst" ,
282288 " LICENSE" ,
@@ -350,6 +356,30 @@ report.exclude_also = [
350356]
351357report.show_missing = true
352358
359+ [tool .towncrier ]
360+ # The changelog and the per-release GitHub release notes are both built
361+ # from news fragments under ``newsfragments/``. The release workflow
362+ # runs ``towncrier build`` to assemble them; contributors add one
363+ # fragment file per user-facing change.
364+ directory = " newsfragments"
365+ filename = " CHANGELOG.rst"
366+ # Custom template so an assembled version reproduces the historical
367+ # style exactly: a bare ``<version>`` heading (no project name, no
368+ # date) followed by a flat bullet list with no per-type sub-headings.
369+ template = " docs/towncrier_template.rst.jinja"
370+ title_format = " {version}"
371+ # ``title_format`` underline first, then any nested headings. A bare
372+ # version such as ``2026.05.18`` underlined with ``-`` matches every
373+ # pre-towncrier entry in CHANGELOG.rst.
374+ underlines = [ " -" , " ~" , " ^" ]
375+ issue_format = " #{issue}"
376+ type = [
377+ # A single, unnamed fragment type keeps the assembled output as one
378+ # flat bullet list, matching the historical changelog (which never
379+ # grouped entries under "Features"/"Bugfixes"/... sub-headings).
380+ { directory = " change" , name = " " , showcontent = true },
381+ ]
382+
353383[tool .pydocstringformatter ]
354384write = true
355385split-summary-body = false
@@ -410,6 +440,9 @@ ignore_names = [
410440 " spelling_word_list_filename" ,
411441 " templates_path" ,
412442 " warning_is_error" ,
443+ " towncrier_draft_autoversion_mode" ,
444+ " towncrier_draft_include_empty" ,
445+ " towncrier_draft_working_directory" ,
413446]
414447exclude = [
415448 # Duplicate some of .gitignore
0 commit comments