diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index e7c3aea..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-diffpy.srmise/_version.py export-subst
diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md
new file mode 100644
index 0000000..b3454de
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_feature.md
@@ -0,0 +1,16 @@
+---
+name: Bug Report or Feature Request
+about: Report a bug or suggest a new feature!
+title: ""
+labels: ""
+assignees: ""
+---
+
+### Problem
+
+<!--
+For a bug report, please copy and paste any error messages from the application or command-line here.
+For a feature request, please state how the new functionality could benefit the community.
+-->
+
+### Proposed solution
diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md
new file mode 100644
index 0000000..a87a44a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/release_checklist.md
@@ -0,0 +1,22 @@
+---
+name: Release
+about: Checklist and communication channel for PyPI and GitHub release
+title: "Ready for <version-number> PyPI/GitHub release"
+labels: "release"
+assignees: ""
+---
+
+### Release checklist for GitHub contributors
+
+- [ ] All PRs/issues attached to the release are merged.
+- [ ] All the badges on the README are passing.
+- [ ] License information is verified as correct. If you are unsure, please comment below.
+- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are
+  missing), tutorials, and other human written text is up-to-date with any changes in the code.
+- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated and
+  tested
+- [ ] Successfully run any tutorial examples or do functional testing in some other way.
+- [ ] Grammar and writing quality have been checked (no typos).
+
+Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as
+version information and details about the pre-release.
diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml
new file mode 100644
index 0000000..5e33817
--- /dev/null
+++ b/.github/workflows/build-wheel-release-upload.yml
@@ -0,0 +1,16 @@
+name: Release (GitHub/PyPI)
+
+on:
+  workflow_dispatch:
+  push:
+    tags:
+      - '*'  # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml
+
+jobs:
+  release:
+    uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0
+    with:
+      project: diffpy.srmise
+    secrets:
+      PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
+      PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
diff --git a/MANIFEST.in b/MANIFEST.in
index 68fc353..f1a78ee 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,11 +1,12 @@
-recursive-include diffpy *.py
-include AUTHORS.txt LICENSE*.txt README.rst
-exclude MANIFEST.in
-include diffpy/srmise/version.cfg
+graft src
+graft tests
+graft requirements
 
-recursive-include doc/examples *.py *.gr *.srmise *.pwa *.dat *.png
-include doc/examples/README
+include AUTHORS.rst LICENSE*.rst README.rst
 
-recursive-include doc/manual/source *.rst *.txt
-include doc/manual/source/conf.py
-include doc/manual/Makefile
+# Exclude all bytecode files and __pycache__ directories
+global-exclude *.py[cod]  # Exclude all .pyc, .pyo, and .pyd files.
+global-exclude .DS_Store  # Exclude Mac filesystem artifacts.
+global-exclude __pycache__  # Exclude Python cache directories.
+global-exclude .git*  # Exclude git files and directories.
+global-exclude .idea  # Exclude PyCharm project settings.
diff --git a/README.rst b/README.rst
index b86c784..967a736 100644
--- a/README.rst
+++ b/README.rst
@@ -72,7 +72,6 @@ must be specified.
 
 For more information about the diffpy.srmise library, please consult our `online documentation <https://diffpy.github.io/diffpy.srmise>`_.
 
-
 Citation
 --------
 
@@ -99,14 +98,16 @@ To add "conda-forge" to the conda channels, run the following in a terminal. ::
 We want to install our packages in a suitable conda environment.
 The following creates and activates a new environment named ``diffpy.srmise_env`` ::
 
-        conda create -n diffpy.srmise_env python=3
+        conda create -n diffpy.srmise_env diffpy.srmise
         conda activate diffpy.srmise_env
 
-Then, to fully install ``diffpy.srmise`` in our active environment, run ::
+To confirm that the installation was successful, type ::
+
+        python -c "import diffpy.srmise; print(diffpy.srmise.__version__)"
 
-        conda install diffpy.srmise
+The output should print the latest version displayed on the badges above.
 
-Another option is to use ``pip`` to download and install the latest release from
+If the above does not work, you can use ``pip`` to download and install the latest release from
 `Python Package Index <https://pypi.python.org>`_.
 To install using ``pip`` into your ``diffpy.srmise_env`` environment, type ::
 
@@ -118,6 +119,11 @@ and run the following ::
 
         pip install .
 
+Getting Started
+---------------
+
+You may consult our `online documentation <https://diffpy.github.io/diffpy.srmise>`_ for tutorials and API references.
+
 Support and Contribute
 ----------------------
 
diff --git a/devutils/makesdist b/devutils/makesdist
deleted file mode 100644
index 380f9e1..0000000
--- a/devutils/makesdist
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-
-"""Create source distribution tar.gz archive, where each file belongs
-to a root user and modification time is set to the git commit time.
-"""
-
-import sys
-import os
-import subprocess
-import time
-import glob
-import tarfile
-
-BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-sys.path.insert(0, BASEDIR)
-
-from setup import versiondata
-timestamp = versiondata.getint('DEFAULT', 'timestamp')
-
-print('Run "setup.py sdist --formats=tar"',)
-cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split()
-ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w'))
-if ec:  sys.exit(ec)
-print("[done]")
-
-tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime)
-
-tfin = tarfile.open(tarname)
-tfout = tarfile.open(tarname + '.gz', 'w:gz')
-
-
-def fixtarinfo(tinfo):
-    tinfo.uid = tinfo.gid = 0
-    tinfo.uname = tinfo.gname = 'root'
-    tinfo.mtime = timestamp
-    tinfo.mode &= ~0o022
-    return tinfo
-
-
-print('Filter %s --> %s.gz' % (2 * (os.path.basename(tarname),)),)
-for ti in tfin:
-    tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti))
-os.remove(tarname)
-print("[done]")
diff --git a/devutils/prep.py b/devutils/prep.py
deleted file mode 100644
index 9eee5e8..0000000
--- a/devutils/prep.py
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/usr/bin/env python
-# Run examples and other scripts that should be updated before release.
-
-import io
-import os
-import re
-import sys
-
-__basedir__ = os.getcwd()
-
-from numpy.compat import unicode
-
-# Example imports
-
-
-class Test:
-
-    def __init__(self):
-        self.messages = []
-
-    def test(self, call, *args, **kwds):
-        m = sys.modules[call.__module__]
-        testname = m.__name__ + "." + call.__name__
-        path = os.path.dirname(m.__file__)
-        os.chdir(path)
-        try:
-            call(*args, **kwds)
-            self.messages.append("%s: success" % testname)
-        except Exception as e:
-            self.messages.append("%s: error, details below.\n%s" % (testname, e))
-        finally:
-            os.chdir(__basedir__)
-
-    def report(self):
-        print("==== Results of Tests ====")
-        print("\n".join(self.messages))
-
-
-def scrubeol(directory, filerestr):
-    """Use unix-style endlines for files in directory matched by regex string.
-
-    Parameters
-    ----------
-    directory - A directory to scrub
-    filerestr - A regex string defining which files to match."""
-    os.chdir(__basedir__)
-    os.chdir(directory)
-    files = [re.match(filerestr, f) for f in os.listdir(".")]
-    files = [f.group(0) for f in files if f]
-
-    for f in files:
-        original = open(f)
-        text = unicode(original.read())
-        original.close()
-
-        updated = io.open(f, "w", newline="\n")
-        updated.write(text)
-        updated.close()
-
-        print("Updated %s to unix-style endlines." % f)
-
-
-def rm(directory, filerestr):
-    """Delete files in directory matched by regex string.
-
-    Parameters
-    ----------
-    directory - A directory to scrub
-    filerestr - A regex string defining which files to match."""
-    os.chdir(__basedir__)
-    os.chdir(directory)
-    files = [re.match(filerestr, f) for f in os.listdir(".")]
-    files = [f.group(0) for f in files if f]
-
-    for f in files:
-        os.remove(f)
-
-        print("Deleted %s." % f)
-
-
-if __name__ == "__main__":
-
-    # Temporarily add examples to path
-    lib_path = os.path.abspath(os.path.join("..", "doc", "examples"))
-    sys.path.append(lib_path)
-
-    # Delete existing files that don't necessarily have a fixed name.
-    rm("../doc/examples/output", r"known_dG.*\.pwa")
-    rm("../doc/examples/output", r"unknown_dG.*\.pwa")
-
-    # Testing examples
-    examples = Test()
-    test_names = [
-        "extract_single_peak",
-        "parameter_summary",
-        "fit_initial",
-        "query_results",
-        "multimodel_known_dG1",
-        "multimodel_known_dG2",
-        "multimodel_unknown_dG1",
-        "multimodel_unknown_dG2",
-    ]
-
-    test_modules = []
-    for test in test_names:
-        test_modules.append(__import__(test))
-
-    for test in test_modules:
-        examples.test(test.run, plot=False)
-
-    examples.report()
-
-    # Convert output of example files to Unix-style endlines for sdist.
-    if os.linesep != "\n":
-        print("==== Scrubbing Endlines ====")
-        # All *.srmise and *.pwa files in examples directory.
-        scrubeol("../doc/examples/output", r".*(\.srmise|\.pwa)")
diff --git a/doc/make.bat b/doc/make.bat
index 2be8306..ac53d5b 100644
--- a/doc/make.bat
+++ b/doc/make.bat
@@ -1,36 +1,36 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-set SPHINXPROJ=PackagingScientificPython
-
-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%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+set SPHINXPROJ=PackagingScientificPython
+
+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%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/doc/source/api/diffpy.srmise.example_package.rst b/doc/source/api/diffpy.srmise.example_package.rst
deleted file mode 100644
index 8c7f6ad..0000000
--- a/doc/source/api/diffpy.srmise.example_package.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-.. _example_package documentation:
-
-|title|
-=======
-
-.. |title| replace:: diffpy.srmise.example_package package
-
-.. automodule:: diffpy.srmise.example_package
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-|foo|
------
-
-.. |foo| replace:: diffpy.srmise.example_package.foo module
-
-.. automodule::  diffpy.srmise.example_package.foo
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-|bar|
------
-
-.. |bar| replace:: diffpy.srmise.example_package.bar module
-
-.. automodule:: diffpy.srmise.example_package.foo
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/doc/source/index.rst b/doc/source/index.rst
index b67293d..417085e 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -23,7 +23,7 @@ https://github.com/diffpy/diffpy.srmise/graphs/contributors.
 Installation
 ============
 
-See the `README <https://github.com/diffpy/diffpy.srmise/blob/main/README.rst>`_
+See the `README <https://github.com/diffpy/diffpy.srmise#installation>`_
 file included with the distribution.
 
 =================
diff --git a/news/recut.rst b/news/recut.rst
new file mode 100644
index 0000000..70e17b5
--- /dev/null
+++ b/news/recut.rst
@@ -0,0 +1,23 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* Recut to group's package standard, fix installation, add GitHub release workflow
+
+**Security:**
+
+* <news item>
diff --git a/pyproject.toml b/pyproject.toml
index 457f28c..2044137 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,7 +48,7 @@ dirty_template = "{tag}"
 [tool.setuptools.packages.find]
 where = ["src"]  # list of folders that contain the packages (["."] by default)
 include = ["*"]  # package names should match these glob patterns (["*"] by default)
-exclude = ["diffpy.srmise.tests*"]  # exclude packages matching these glob patterns (empty by default)
+exclude = []  # exclude packages matching these glob patterns (empty by default)
 namespaces = false  # to disable scanning PEP 420 namespaces (true by default)
 
 [tool.setuptools.dynamic]