@@ -76,13 +76,17 @@ optional-dependencies.dev = [
7676 " sphinx-pyproject==0.3.0" ,
7777 " sphinx-substitution-extensions==2026.1.12" ,
7878 " sphinxcontrib-spelling==8.0.2" ,
79+ # ``sphinxcontrib-towncrier`` renders unreleased news fragments
80+ # into docs/source/unreleased.rst during Sphinx builds.
81+ " sphinxcontrib-towncrier==0.5.0a0" ,
7982 " strict-kwargs==2026.5.19.post3" ,
8083 " sybil==9.3.0" ,
8184 # Listed explicitly (despite being transitive via vws-python-mock) so that
8285 # [tool.uv.sources] can redirect to the CPU-only PyTorch index.
8386 # See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation
8487 " torch>=2.5.1" ,
8588 " torchvision>=0.20.1" ,
89+ " towncrier==25.8.0" ,
8690 " ty==0.0.37" ,
8791 " types-requests==2.33.0.20260518" ,
8892 " vulture==2.16" ,
@@ -284,6 +288,8 @@ ignore = [
284288 " *.enc" ,
285289 " .pre-commit-config.yaml" ,
286290 " CHANGELOG.rst" ,
291+ " newsfragments" ,
292+ " newsfragments/**" ,
287293 " CODE_OF_CONDUCT.rst" ,
288294 " CONTRIBUTING.rst" ,
289295 " LICENSE" ,
@@ -349,6 +355,30 @@ report.exclude_also = [
349355]
350356report.show_missing = true
351357
358+ [tool .towncrier ]
359+ # The changelog and the per-release GitHub release notes are both built
360+ # from news fragments under ``newsfragments/``. The release workflow
361+ # runs ``towncrier build`` to assemble them; contributors add one
362+ # fragment file per user-facing change.
363+ directory = " newsfragments"
364+ filename = " CHANGELOG.rst"
365+ # Custom template so an assembled version reproduces the historical
366+ # style exactly: a bare ``<version>`` heading (no project name, no
367+ # date) followed by a flat bullet list with no per-type sub-headings.
368+ template = " docs/towncrier_template.rst.jinja"
369+ title_format = " {version}"
370+ # ``title_format`` underline first, then any nested headings. A bare
371+ # version such as ``2026.05.18`` underlined with ``-`` matches every
372+ # pre-towncrier entry in CHANGELOG.rst.
373+ underlines = [ " -" , " ~" , " ^" ]
374+ issue_format = " #{issue}"
375+ type = [
376+ # A single, unnamed fragment type keeps the assembled output as one
377+ # flat bullet list, matching the historical changelog (which never
378+ # grouped entries under "Features"/"Bugfixes"/... sub-headings).
379+ { directory = " change" , name = " " , showcontent = true },
380+ ]
381+
352382[tool .pydocstringformatter ]
353383write = true
354384split-summary-body = false
@@ -413,6 +443,9 @@ ignore_names = [
413443 " spelling_word_list_filename" ,
414444 " templates_path" ,
415445 " warning_is_error" ,
446+ " towncrier_draft_autoversion_mode" ,
447+ " towncrier_draft_include_empty" ,
448+ " towncrier_draft_working_directory" ,
416449]
417450# Duplicate some of .gitignore
418451exclude = [ " .venv" ]
0 commit comments