diff --git a/.flake8 b/.flake8
index 2d028b2d..2ea73951 100644
--- a/.flake8
+++ b/.flake8
@@ -1,7 +1,6 @@
 [flake8]
 filename =
-    *.py,
-    *.pys
+    *.py
 max-line-length = 120
 extend-exclude =
     venv/
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b736c94c..75b9d1db 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -18,6 +18,20 @@ updates:
       interval: "daily"
       time: "08:30"
     open-pull-requests-limit: 10
+    groups:
+      docker-actions:
+        applies-to: version-updates
+        patterns:
+          - "docker/*"
+      github-actions:
+        applies-to: version-updates
+        patterns:
+          - "actions/*"
+          - "github/*"
+      lizardbyte-actions:
+        applies-to: version-updates
+        patterns:
+          - "LizardByte/*"
 
   - package-ecosystem: "npm"
     directory: "/"
@@ -25,6 +39,10 @@ updates:
       interval: "daily"
       time: "09:00"
     open-pull-requests-limit: 10
+    groups:
+      dev-dependencies:
+        applies-to: version-updates
+        dependency-type: "development"
 
   - package-ecosystem: "nuget"
     directory: "/"
@@ -39,6 +57,11 @@ updates:
       interval: "daily"
       time: "10:00"
     open-pull-requests-limit: 10
+    groups:
+      pytest-dependencies:
+        applies-to: version-updates
+        patterns:
+          - "pytest*"
 
   - package-ecosystem: "gitsubmodule"
     directory: "/"
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
index 124febc4..fd967709 100644
--- a/.github/workflows/ci-docker.yml
+++ b/.github/workflows/ci-docker.yml
@@ -115,39 +115,11 @@ jobs:
 
       - name: Setup Release
         id: setup_release
-        uses: LizardByte/setup-release-action@v2024.801.192524
+        uses: LizardByte/setup-release-action@v2024.919.143601
         with:
           dotnet: ${{ needs.check_dockerfiles.outputs.dotnet }}
           github_token: ${{ secrets.GITHUB_TOKEN }}
 
-  lint_dockerfile:
-    needs: [check_dockerfiles]
-    if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
-    runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }}
-    name: Lint Dockerfile${{ matrix.tag }}
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Hadolint
-        id: hadolint
-        uses: hadolint/hadolint-action@v3.1.0
-        with:
-          dockerfile: ${{ matrix.dockerfile }}
-          ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059
-          output-file: ./hadolint.log
-          verbose: true
-
-      - name: Log
-        if: failure()
-        run: |
-          echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY
-          cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY
-
   docker:
     needs: [check_dockerfiles, setup_release]
     if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
@@ -362,12 +334,11 @@ jobs:
 
       - name: Create/Update GitHub Release
         if: ${{ needs.setup_release.outputs.publish_release == 'true' && steps.prepare.outputs.artifacts == 'true' }}
-        uses: LizardByte/create-release-action@v2024.614.221009
+        uses: LizardByte/create-release-action@v2024.919.143026
         with:
           allowUpdates: true
           artifacts: "*artifacts/*"
           body: ${{ needs.setup_release.outputs.release_body }}
-          discussionCategory: announcements
           generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
           name: ${{ needs.setup_release.outputs.release_tag }}
           prerelease: true
diff --git a/.github/workflows/common-lint.yml b/.github/workflows/common-lint.yml
new file mode 100644
index 00000000..4a62b245
--- /dev/null
+++ b/.github/workflows/common-lint.yml
@@ -0,0 +1,245 @@
+---
+# This action is centrally managed in https://github.com/<organization>/.github/
+# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
+# the above-mentioned repo.
+
+# Common linting.
+
+name: common lint
+
+on:
+  pull_request:
+    branches: [master]
+    types: [opened, synchronize, reopened]
+
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
+jobs:
+  lint:
+    name: Common Lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.12'
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade \
+            pip \
+            setuptools \
+            wheel \
+            cmakelang \
+            flake8 \
+            nb-clean \
+            nbqa[toolchain]
+
+      - name: C++ - find files
+        id: cpp_files
+        run: |
+          # find files
+          found_files=$(find . -type f \
+            -iname "*.c" -o \
+            -iname "*.cpp" -o \
+            -iname "*.h" -o \
+            -iname "*.hpp" -o \
+            -iname "*.m" -o \
+            -iname "*.mm" \
+          )
+          ignore_files=$(find . -type f -iname ".clang-format-ignore")
+
+          # Loop through each C++ file
+          for file in $found_files; do
+            for ignore_file in $ignore_files; do
+              ignore_directory=$(dirname "$ignore_file")
+              # if directory of ignore_file is beginning of file
+              if [[ "$file" == "$ignore_directory"* ]]; then
+                echo "ignoring file: ${file}"
+                found_files="${found_files//${file}/}"
+                break 1
+              fi
+            done
+          done
+
+          # remove empty lines
+          found_files=$(echo "$found_files" | sed '/^\s*$/d')
+
+          echo "found cpp files: ${found_files}"
+
+          # do not quote to keep this as a single line
+          echo found_files=${found_files} >> $GITHUB_OUTPUT
+
+      - name: C++ - Clang format lint
+        if: always() && steps.cpp_files.outputs.found_files
+        uses: DoozyX/clang-format-lint-action@v0.18
+        with:
+          source: ${{ steps.cpp_files.outputs.found_files }}
+          extensions: 'c,cpp,h,hpp,m,mm'
+          style: file
+          inplace: false
+
+      - name: CMake - find files
+        id: cmake_files
+        if: always()
+        run: |
+          # find files
+          found_files=$(find . -type f -iname "CMakeLists.txt" -o -iname "*.cmake")
+          ignore_files=$(find . -type f -iname ".cmake-lint-ignore")
+
+          # Loop through each C++ file
+          for file in $found_files; do
+            for ignore_file in $ignore_files; do
+              ignore_directory=$(dirname "$ignore_file")
+              # if directory of ignore_file is beginning of file
+              if [[ "$file" == "$ignore_directory"* ]]; then
+                echo "ignoring file: ${file}"
+                found_files="${found_files//${file}/}"
+                break 1
+              fi
+            done
+          done
+
+          # remove empty lines
+          found_files=$(echo "$found_files" | sed '/^\s*$/d')
+
+          echo "found cmake files: ${found_files}"
+
+          # do not quote to keep this as a single line
+          echo found_files=${found_files} >> $GITHUB_OUTPUT
+
+      - name: CMake - cmake-lint
+        if: always() && steps.cmake_files.outputs.found_files
+        run: |
+          cmake-lint --line-width 120 --tab-size 4 ${{ steps.cmake_files.outputs.found_files }}
+
+      - name: Docker - find files
+        id: dokcer_files
+        if: always()
+        run: |
+          found_files=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile")
+
+          echo "found_files: ${found_files}"
+
+          # do not quote to keep this as a single line
+          echo found_files=${found_files} >> $GITHUB_OUTPUT
+
+      - name: Docker - hadolint
+        if: always() && steps.dokcer_files.outputs.found_files
+        run: |
+          docker pull hadolint/hadolint
+
+          # create hadolint config file
+          cat <<EOF > .hadolint.yaml
+          ---
+          ignored:
+            - DL3008
+            - DL3013
+            - DL3016
+            - DL3018
+            - DL3028
+            - DL3059
+          EOF
+
+          failed=0
+          failed_files=""
+
+          for file in ${{ steps.dokcer_files.outputs.found_files }}; do
+            echo "::group::${file}"
+            docker run --rm -i \
+              -e "NO_COLOR=0" \
+              -e "HADOLINT_VERBOSE=1" \
+              -v $(pwd)/.hadolint.yaml:/.config/hadolint.yaml \
+              hadolint/hadolint < $file || {
+                failed=1
+                failed_files="$failed_files $file"
+              }
+            echo "::endgroup::"
+          done
+
+          if [ $failed -ne 0 ]; then
+            echo "::error:: hadolint failed for the following files: $failed_files"
+            exit 1
+          fi
+
+      - name: Python - flake8
+        if: always()
+        run: |
+          python -m flake8 \
+            --color=always \
+            --verbose
+
+      - name: Python - nbqa flake8
+        if: always()
+        run: |
+          python -m nbqa flake8 \
+            --color=always \
+            --verbose \
+            .
+
+      - name: Python - nb-clean
+        if: always()
+        run: |
+          output=$(find . -name '*.ipynb' -exec nb-clean check {} \;)
+
+          # fail if there are any issues
+          if [ -n "$output" ]; then
+            echo "$output"
+            exit 1
+          fi
+
+      - name: YAML - find files
+        id: yaml_files
+        if: always()
+        run: |
+          # space separated list of files
+          FILES=.clang-format
+
+          # empty placeholder
+          found_files=""
+
+          for FILE in ${FILES}; do
+            if [ -f "$FILE" ]
+            then
+              found_files="$found_files $FILE"
+            fi
+          done
+
+          echo "found_files=${found_files}" >> $GITHUB_OUTPUT
+
+      - name: YAML - yamllint
+        id: yamllint
+        if: always()
+        uses: ibiqlik/action-yamllint@v3
+        with:
+          # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
+          config_data: |
+            extends: default
+            rules:
+              comments:
+                level: error
+              document-start:
+                level: error
+              line-length:
+                max: 120
+              new-line-at-end-of-file:
+                level: error
+              new-lines:
+                type: unix
+              truthy:
+                # GitHub uses "on" for workflow event triggers
+                # .clang-format file has options of "Yes" "No" that will be caught by this, so changed to "warning"
+                allowed-values: ['true', 'false', 'on']
+                check-keys: true
+                level: warning
+          file_or_dir: . ${{ steps.yaml_files.outputs.found_files }}
+
+      - name: YAML - log
+        if: always() && steps.yamllint.outcome == 'failure'
+        run: |
+          cat "${{ steps.yamllint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/python-flake8.yml b/.github/workflows/python-flake8.yml
deleted file mode 100644
index 61e23f74..00000000
--- a/.github/workflows/python-flake8.yml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-# This action is centrally managed in https://github.com/<organization>/.github/
-# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
-# the above-mentioned repo.
-
-# Lint python files with flake8.
-
-name: flake8
-
-on:
-  pull_request:
-    branches: [master]
-    types: [opened, synchronize, reopened]
-
-concurrency:
-  group: "${{ github.workflow }}-${{ github.ref }}"
-  cancel-in-progress: true
-
-jobs:
-  flake8:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Set up Python
-        uses: actions/setup-python@v5  # https://github.com/actions/setup-python
-        with:
-          python-version: '3.10'
-
-      - name: Install dependencies
-        run: |
-          # pin flake8 before v6.0.0 due to removal of support for type comments (required for Python 2.7 type hints)
-          python -m pip install --upgrade pip setuptools "flake8<6"
-
-      - name: Test with flake8
-        run: |
-          python -m flake8 --verbose
diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml
index 2d1e632d..1ac8a3a3 100644
--- a/.github/workflows/release-notifier.yml
+++ b/.github/workflows/release-notifier.yml
@@ -28,7 +28,7 @@ jobs:
         id: output
         run: |
           echo "${RELEASE_BODY}" > ./release_body.md
-          modified_body=$(sed '/^---$/d; /^## Contributors$/,/<\/a>/d' ./release_body.md)
+          modified_body=$(sed '/^---/,$d' ./release_body.md)
           echo "modified_body: ${modified_body}"
 
           # use a heredoc to ensure the output is multiline
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
index 99d2793e..3c095fcf 100644
--- a/.github/workflows/update-changelog.yml
+++ b/.github/workflows/update-changelog.yml
@@ -24,7 +24,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Update Changelog
-        uses: LizardByte/update-changelog-action@v2024.609.4705
+        uses: LizardByte/update-changelog-action@v2024.919.152649
         with:
           changelogBranch: changelog
           changelogFile: CHANGELOG.md
diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml
deleted file mode 100644
index 023b836c..00000000
--- a/.github/workflows/yaml-lint.yml
+++ /dev/null
@@ -1,66 +0,0 @@
----
-# This action is centrally managed in https://github.com/<organization>/.github/
-# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
-# the above-mentioned repo.
-
-# Lint yaml files.
-
-name: yaml lint
-
-on:
-  pull_request:
-    branches: [master]
-    types: [opened, synchronize, reopened]
-
-concurrency:
-  group: "${{ github.workflow }}-${{ github.ref }}"
-  cancel-in-progress: true
-
-jobs:
-  yaml-lint:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Find additional files
-        id: find-files
-        run: |
-          # space separated list of files
-          FILES=.clang-format
-
-          # empty placeholder
-          FOUND=""
-
-          for FILE in ${FILES}; do
-            if [ -f "$FILE" ]
-            then
-              FOUND="$FOUND $FILE"
-            fi
-          done
-
-          echo "found=${FOUND}" >> $GITHUB_OUTPUT
-
-      - name: yaml lint
-        id: yaml-lint
-        uses: ibiqlik/action-yamllint@v3
-        with:
-          # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
-          config_data: |
-            extends: default
-            rules:
-              comments:
-                level: error
-              line-length:
-                max: 120
-              truthy:
-                # GitHub uses "on" for workflow event triggers
-                # .clang-format file has options of "Yes" "No" that will be caught by this, so changed to "warning"
-                allowed-values: ['true', 'false', 'on']
-                check-keys: true
-                level: warning
-          file_or_dir: . ${{ steps.find-files.outputs.found }}
-
-      - name: Log
-        run: |
-          cat "${{ steps.yaml-lint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY