diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 00000000..f3bbc413
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,13 @@
+==================
+Changelog
+==================
+
+Versions follow `Semantic Versioning `_ (``..``),
+but with minor syntax differences to satisfy python package
+`version specifiers `_.
+
+Until a stable version is released (end of beta), new versions may contain backward-incompatible changes,
+but we will strive to deprecate features first instead of immediately removal.
+After that, breaking changes will only be introduced in major versions.
+
+.. include:: changelog/changelog_body.rst
diff --git a/docs/changelog/changelog_body.rst b/docs/changelog/changelog_body.rst
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/changelog/fragments/README.rst b/docs/changelog/fragments/README.rst
new file mode 100644
index 00000000..34e251fd
--- /dev/null
+++ b/docs/changelog/fragments/README.rst
@@ -0,0 +1,26 @@
+This directory contains "news fragments" which are short files that contain a small **ReST**-formatted
+text that will be added to the next ``CHANGELOG``.
+
+The ``CHANGELOG`` will be read by **users**, so this description should be aimed to pytest users
+instead of describing internal changes which are only relevant to the developers.
+
+Make sure to use full sentences in the **past or present tense** and use punctuation, examples::
+
+ Improved verbose diff output with sequences.
+
+ Terminal summary statistics now use multiple colors.
+
+Each file should be named like ``..rst``, where
+```` is an issue number, and ```` is one of:
+
+* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
+* ``deprecation``: feature deprecation.
+* ``feature``: new user facing features, like new command-line options and new behavior.
+* ``bugfix``: fixes a bug.
+* ``other``: any
+
+So for example: ``123.feature.rst``, ``456.bugfix.rst``.
+
+If your PR fixes an issue, use that number here. If there is no issue,
+then after you submit the PR and get the PR number you can add a
+changelog using that instead.
diff --git a/docs/changelog/template.rst.jinja2 b/docs/changelog/template.rst.jinja2
new file mode 100644
index 00000000..d09721f7
--- /dev/null
+++ b/docs/changelog/template.rst.jinja2
@@ -0,0 +1,44 @@
+{% if render_title %}
+----------------------------------------------------
+
+
+.. _v{{ versiondata.version }}:
+
+`{{ versiondata.version }} `_ -- {{ versiondata.date }}
+{{ top_underline * ((versiondata.version * 2 + versiondata.date)|length + 52) }}
+{% endif %}
+{% for section, _ in sections.items() %}
+{% set underline = underlines[0] %}{% if section %}{{section}}
+{{ underline * section|length }}{% set underline = underlines[1] %}
+
+{% endif %}
+
+{% if sections[section] %}
+{% for category, val in definitions.items() if category in sections[section] %}
+.. _v{{ versiondata.version }}-{{ definitions[category]['name'] }}:
+
+{{ definitions[category]['name'] }}
+{{ underline * definitions[category]['name']|length }}
+
+{% if definitions[category]['showcontent'] %}
+{% for text, values in sections[section][category].items() %}
+- {{ text }}{% if values %} ({{ values|join(', ') }}){% endif %}
+
+{% endfor %}
+{% else %}
+- {{ sections[section][category]['']|join(', ') }}
+
+{% endif %}
+{% if sections[section][category]|length == 0 %}
+No significant changes.
+
+{% else %}
+{% endif %}
+
+{% endfor %}
+{% else %}
+No significant changes.
+
+
+{% endif %}
+{% endfor %}
diff --git a/docs/conf.py b/docs/conf.py
index 488b0743..0a366960 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -49,6 +49,7 @@
'sphinx_paramlinks',
'myst_parser',
'sphinxext.opengraph',
+ 'sphinx_better_subsection',
# local extensions
'custom_ext.bench_tools',
@@ -60,7 +61,9 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = []
+exclude_patterns = [
+ 'changelog/fragments/*'
+]
# -- Options for HTML output -------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index c4405355..68143e65 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,6 +12,7 @@ Adaptix
specific_types_behavior
benchmarks
API Reference
+ changelog
contributing
@@ -19,7 +20,7 @@ Adaptix
:maxdepth: 2
:caption: Project Links:
- GitHub
+ GitHub
PyPI
diff --git a/make.bat b/make.bat
deleted file mode 100644
index 6247f7e2..00000000
--- a/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/pyproject.toml b/pyproject.toml
index 25a43c57..c6c937d3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,3 +76,19 @@ skips = ['*/test_*.py', '*/*_test.py', 'docs/examples/*', '*/bench_*.py']
[tool.vulture]
min_confidence = 60
ignore_decorators = ['@_aspect_storage.add', '@overload', '@abstractmethod']
+
+
+[tool.towncrier]
+package = 'adaptix'
+filename = 'docs/changelog/changelog_body.rst'
+template = 'docs/changelog/template.rst.jinja2'
+issue_format = '`#{issue} `_'
+directory = 'docs/changelog/fragments'
+
+type = [
+ { name = "Breaking Changes", directory = "breaking", showcontent = true },
+ { name = "Deprecations", directory = "deprecation", showcontent = true },
+ { name = "Features", directory = "feature", showcontent = true },
+ { name = "Bug Fixes", directory = "bugfix", showcontent = true },
+ { name = "Other", directory = "other", showcontent = true },
+]
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 8cadf1e8..1150279d 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -44,7 +44,9 @@ chardet==5.2.0
charset-normalizer==3.3.2
# via requests
click==8.1.7
- # via pip-tools
+ # via
+ # pip-tools
+ # towncrier
cognitive-complexity==1.3.0
# via flake8-cognitive-complexity
colorama==0.4.6
@@ -72,6 +74,7 @@ docutils==0.20.1
# docutils-stubs
# myst-parser
# sphinx
+ # sphinx-better-subsection
# sphinx-paramlinks
docutils-stubs==0.0.22
# via -r requirements/raw/doc.txt
@@ -106,7 +109,7 @@ flake8-noqa==1.3.2
# via -r requirements/raw/lint.txt
flake8-polyfill==1.0.2
# via -r requirements/raw/lint.txt
-fonttools==4.46.0
+fonttools==4.47.0
# via matplotlib
furo==2023.9.10
# via -r requirements/raw/doc.txt
@@ -126,6 +129,8 @@ idna==3.6
# via requests
imagesize==1.4.1
# via sphinx
+incremental==22.10.0
+ # via towncrier
iniconfig==2.0.0
# via pytest
invoke==2.2.0
@@ -136,9 +141,10 @@ jinja2==3.1.2
# via
# myst-parser
# sphinx
+ # towncrier
kiwisolver==1.4.5
# via matplotlib
-lxml==4.9.3
+lxml==4.9.4
# via astpath
mando==0.7.1
# via radon
@@ -290,6 +296,8 @@ sphinx==7.2.6
# sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
+sphinx-better-subsection==0.2
+ # via -r requirements/raw/doc.txt
sphinx-copybutton==0.5.2
# via -r requirements/raw/doc.txt
sphinx-design==0.5.0
@@ -322,6 +330,8 @@ toml==0.10.2
# via vulture
tomlkit==0.12.3
# via pylint
+towncrier==23.11.0
+ # via -r requirements/raw/dev.txt
tox==4.11.4
# via -r requirements/raw/runner.txt
typing-extensions==4.9.0
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 2e57bc16..0bbf6583 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -29,6 +29,7 @@ docutils==0.20.1
# docutils-stubs
# myst-parser
# sphinx
+ # sphinx-better-subsection
# sphinx-paramlinks
docutils-stubs==0.0.22
# via -r requirements/raw/doc.txt
@@ -124,6 +125,8 @@ sphinx==7.2.6
# sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
+sphinx-better-subsection==0.2
+ # via -r requirements/raw/doc.txt
sphinx-copybutton==0.5.2
# via -r requirements/raw/doc.txt
sphinx-design==0.5.0
diff --git a/requirements/lint.txt b/requirements/lint.txt
index a9c9b1d3..7d34ce87 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -60,6 +60,7 @@ docutils==0.20.1
# docutils-stubs
# myst-parser
# sphinx
+ # sphinx-better-subsection
# sphinx-paramlinks
docutils-stubs==0.0.22
# via -r requirements/raw/doc.txt
@@ -92,7 +93,7 @@ flake8-noqa==1.3.2
# via -r requirements/raw/lint.txt
flake8-polyfill==1.0.2
# via -r requirements/raw/lint.txt
-fonttools==4.46.0
+fonttools==4.47.0
# via matplotlib
furo==2023.9.10
# via -r requirements/raw/doc.txt
@@ -122,7 +123,7 @@ jinja2==3.1.2
# sphinx
kiwisolver==1.4.5
# via matplotlib
-lxml==4.9.3
+lxml==4.9.4
# via astpath
mando==0.7.1
# via radon
@@ -262,6 +263,8 @@ sphinx==7.2.6
# sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
+sphinx-better-subsection==0.2
+ # via -r requirements/raw/doc.txt
sphinx-copybutton==0.5.2
# via -r requirements/raw/doc.txt
sphinx-design==0.5.0
diff --git a/requirements/raw/dev.txt b/requirements/raw/dev.txt
index 02a1e0eb..92020247 100644
--- a/requirements/raw/dev.txt
+++ b/requirements/raw/dev.txt
@@ -1,6 +1,7 @@
coverage==7.3.3
pip-tools==7.3.0
setuptools==69.0.0
+towncrier==23.11.0
-r runner.txt
-r lint.txt
diff --git a/requirements/raw/doc.txt b/requirements/raw/doc.txt
index 7dcb40b3..f9fb2676 100644
--- a/requirements/raw/doc.txt
+++ b/requirements/raw/doc.txt
@@ -6,6 +6,7 @@ sphinx-design==0.5.0
sphinx-paramlinks==0.6.0
myst-parser==2.0.0
sphinxext-opengraph==0.9.1
+sphinx-better-subsection==0.2
sphinxcontrib-apidoc==0.4.0
furo==2023.9.10