build(dev): move release management tooling from //tools/private to //dev (#4153)

Consolidate repository release management tooling under //dev as part
of organizing developer-only tools into a dedicated package. Retaining
developer utilities in //tools/private conflates repo maintenance tools
with internal runtime dependencies of //tools.

Move release CLI and library targets from //tools/private/release to
//dev/release. Update Python imports, unit tests, GitHub Actions
workflows, and documentation to reference the new package location.
Clarify the scope of //tools/private in its documentation.

Work towards #4128.
diff --git a/.agents/rules/pr.md b/.agents/rules/pr.md
index 7c49497..5770597 100644
--- a/.agents/rules/pr.md
+++ b/.agents/rules/pr.md
@@ -17,7 +17,7 @@
 ## Commit & PR Types
 * `fix:` / `feat:`: User-visible changes ONLY.
 * `build:` / `build(release):`: Internal release tooling
-  (`tools/private/release/`), developer workflows, and build scripts. Always
+  (`dev/release/`), developer workflows, and build scripts. Always
   prefer `build(release):` over `chore:` or `chore(release):`.
 * `docs:`: Documentation and issue templates (`.github/ISSUE_TEMPLATE/`).
 * `ci:`: `.bazelci` and Buildkite CI configurations.
diff --git a/.github/workflows/backport_create_releases.yaml b/.github/workflows/backport_create_releases.yaml
index 346bfdb..6267cf5 100644
--- a/.github/workflows/backport_create_releases.yaml
+++ b/.github/workflows/backport_create_releases.yaml
@@ -43,5 +43,5 @@
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           ISSUE: ${{ inputs.issue }}
         run: |
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             backport-create-releases --issue=$ISSUE --no-dry-run
diff --git a/.github/workflows/backport_prepare.yaml b/.github/workflows/backport_prepare.yaml
index 587c0fc..22543b5 100644
--- a/.github/workflows/backport_prepare.yaml
+++ b/.github/workflows/backport_prepare.yaml
@@ -72,5 +72,5 @@
               ARGS+=("--to-minor=$TO_MINOR")
             fi
           fi
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             backport-prepare "${ARGS[@]}" --no-dry-run
diff --git a/.github/workflows/on_pr_closed.yaml b/.github/workflows/on_pr_closed.yaml
index 7466f82..c01d1b8 100644
--- a/.github/workflows/on_pr_closed.yaml
+++ b/.github/workflows/on_pr_closed.yaml
@@ -63,7 +63,7 @@
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           PR_NUMBER: ${{ needs.parse_pr.outputs.pr_number }}
         run: |
-          bazel run //tools/private/release -- on-pr-merged \
+          bazel run //dev/release -- on-pr-merged \
             "$PR_NUMBER" \
             --remote origin \
             --no-dry-run
diff --git a/.github/workflows/release_add_backports.yaml b/.github/workflows/release_add_backports.yaml
index d9d6d84..6cfd2fa 100644
--- a/.github/workflows/release_add_backports.yaml
+++ b/.github/workflows/release_add_backports.yaml
@@ -53,6 +53,6 @@
           # Convert CSV to array
           IFS=',' read -r -a pr_array <<< "$PRS"
           
-          bazel run //tools/private/release -- add-backports \
+          bazel run //dev/release -- add-backports \
             "${pr_array[@]}" \
             "${ARGS[@]}"
diff --git a/.github/workflows/release_complete_prepare.yaml b/.github/workflows/release_complete_prepare.yaml
index 2496dd9..8315057 100644
--- a/.github/workflows/release_complete_prepare.yaml
+++ b/.github/workflows/release_complete_prepare.yaml
@@ -67,7 +67,7 @@
             ARGS+=("--issue=$ISSUE")
           fi
           # Run the complete-prepare subcommand in the release tool to cleanly update checklist metadata
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             complete-prepare "${ARGS[@]}"
 
   call_create_release_branch:
diff --git a/.github/workflows/release_create_rc.yaml b/.github/workflows/release_create_rc.yaml
index a96a04a..1915a84 100644
--- a/.github/workflows/release_create_rc.yaml
+++ b/.github/workflows/release_create_rc.yaml
@@ -58,7 +58,7 @@
           if [ -n "$COMMENT_ID" ]; then
             ARGS+=("--triggering-comment=$COMMENT_ID")
           fi
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             create-rc --issue "$ISSUE" --remote origin "${ARGS[@]}"
 
   call_release:
diff --git a/.github/workflows/release_create_release_branch.yaml b/.github/workflows/release_create_release_branch.yaml
index d251b1e..e4f271b 100644
--- a/.github/workflows/release_create_release_branch.yaml
+++ b/.github/workflows/release_create_release_branch.yaml
@@ -41,7 +41,7 @@
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             create-release-branch --issue "${{ inputs.issue }}" --remote origin
 
   # A no-op job that always runs to prevent "no jobs ran" failures
diff --git a/.github/workflows/release_prepare.yaml b/.github/workflows/release_prepare.yaml
index c83a7b7..fa99bc7 100644
--- a/.github/workflows/release_prepare.yaml
+++ b/.github/workflows/release_prepare.yaml
@@ -47,5 +47,5 @@
           if [ -n "$ISSUE" ]; then
             ARGS+=("--issue=$ISSUE")
           fi
-          bazel run //tools/private/release -- \
+          bazel run //dev/release -- \
             prepare "${ARGS[@]}" --no-dry-run
diff --git a/.github/workflows/release_process_backports.yaml b/.github/workflows/release_process_backports.yaml
index df23739..49b3412 100644
--- a/.github/workflows/release_process_backports.yaml
+++ b/.github/workflows/release_process_backports.yaml
@@ -70,7 +70,7 @@
             ARGS+=("--triggering-comment=$COMMENT_ID")
           fi
           
-          bazel run //tools/private/release -- process-backports \
+          bazel run //dev/release -- process-backports \
             --issue "$ISSUE" \
             --remote origin \
             --no-dry-run \
diff --git a/.github/workflows/release_promote.yaml b/.github/workflows/release_promote.yaml
index 93ec23a..87989bd 100644
--- a/.github/workflows/release_promote.yaml
+++ b/.github/workflows/release_promote.yaml
@@ -60,7 +60,7 @@
           ARGS+=("--remote=origin")
           ARGS+=("--no-dry-run")
           
-          bazel run //tools/private/release -- promote "${ARGS[@]}"
+          bazel run //dev/release -- promote "${ARGS[@]}"
 
   publish:
     needs: promote
diff --git a/.github/workflows/release_sync_changelog.yaml b/.github/workflows/release_sync_changelog.yaml
index 70f3cf8..bc1fc8c 100644
--- a/.github/workflows/release_sync_changelog.yaml
+++ b/.github/workflows/release_sync_changelog.yaml
@@ -42,5 +42,5 @@
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
-          bazel run //tools/private/release -- sync-changelog \
+          bazel run //dev/release -- sync-changelog \
             --remote origin
diff --git a/.github/workflows/release_sync_changelog_complete.yaml b/.github/workflows/release_sync_changelog_complete.yaml
index e210f3b..f8d5082 100644
--- a/.github/workflows/release_sync_changelog_complete.yaml
+++ b/.github/workflows/release_sync_changelog_complete.yaml
@@ -42,4 +42,4 @@
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
-          bazel run //tools/private/release -- complete-sync-changelog
+          bazel run //dev/release -- complete-sync-changelog
diff --git a/RELEASING.md b/RELEASING.md
index 34ea40f..2f94fde 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -123,7 +123,7 @@
 ### Method D: Release Tool CLI
 You can use the release tool to add backports from your local checkout:
 ```shell
-bazel run //tools/private/release -- add-backports <PR_REF> [<PR_REF> ...]
+bazel run //dev/release -- add-backports <PR_REF> [<PR_REF> ...]
 ```
 The `<PR_REF>` can be:
 *   A PR number (e.g., `124` or `#124`)
@@ -138,7 +138,7 @@
 the PR:
 
 ```shell
-bazel run //tools/private/release -- \
+bazel run //dev/release -- \
     process-news <VERSION> <TARGET> [<TARGET> ...]
 ```
 
@@ -156,10 +156,10 @@
 
 ```shell
 # Process a single news file
-bazel run //tools/private/release -- process-news 2.3.0 news/3997.added.md
+bazel run //dev/release -- process-news 2.3.0 news/3997.added.md
 
 # Process all news files and update version markers for a PR
-bazel run //tools/private/release -- process-news 2.3.0 3997
+bazel run //dev/release -- process-news 2.3.0 3997
 ```
 
 ### Syncing Changelog to Main
@@ -181,7 +181,7 @@
 release tool CLI:
 
 ```shell
-bazel run //tools/private/release -- \
+bazel run //dev/release -- \
     sync-changelog --issue <ISSUE> --remote origin
 ```
 
diff --git a/tools/private/release/BUILD.bazel b/dev/release/BUILD.bazel
similarity index 71%
rename from tools/private/release/BUILD.bazel
rename to dev/release/BUILD.bazel
index d484c01..c51aca6 100644
--- a/tools/private/release/BUILD.bazel
+++ b/dev/release/BUILD.bazel
@@ -1,10 +1,12 @@
-load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+load("//python:py_binary.bzl", "py_binary")
+load("//python:py_library.bzl", "py_library")
 
-package(default_visibility = ["//visibility:public"])
+package(default_visibility = ["//:__subpackages__"])
 
 py_library(
     name = "changelog_news",
     srcs = ["changelog_news.py"],
+    imports = ["../.."],
 )
 
 py_library(
@@ -16,6 +18,7 @@
             "mock_gh.py",
         ],
     ),
+    imports = ["../.."],
     deps = [
         ":changelog_news",
         "@dev_pip//packaging",
@@ -25,6 +28,7 @@
 py_library(
     name = "mock_gh",
     srcs = ["mock_gh.py"],
+    imports = ["../.."],
     deps = [
         ":release_lib",
     ],
@@ -33,6 +37,7 @@
 py_binary(
     name = "release",
     srcs = ["release.py"],
+    imports = ["../.."],
     main = "release.py",
     python_version = "3.14",
     deps = [
diff --git a/tools/private/release/__init__.py b/dev/release/__init__.py
similarity index 100%
rename from tools/private/release/__init__.py
rename to dev/release/__init__.py
diff --git a/tools/private/release/add_backports.py b/dev/release/add_backports.py
similarity index 96%
rename from tools/private/release/add_backports.py
rename to dev/release/add_backports.py
index a24e02d..78bcf78 100644
--- a/tools/private/release/add_backports.py
+++ b/dev/release/add_backports.py
@@ -1,8 +1,8 @@
 import os
 
-from tools.private.release.gh import GitHub
-from tools.private.release.git import Git
-from tools.private.release.release_issue import (
+from dev.release.gh import GitHub
+from dev.release.git import Git
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     add_backports_to_body,
     add_rc_task_to_body,
@@ -10,7 +10,7 @@
     load_release_tracking_template,
     parse_checklist_state,
 )
-from tools.private.release.utils import determine_next_version
+from dev.release.utils import determine_next_version
 
 
 class AddBackports:
diff --git a/tools/private/release/backport_create_releases.py b/dev/release/backport_create_releases.py
similarity index 97%
rename from tools/private/release/backport_create_releases.py
rename to dev/release/backport_create_releases.py
index 1a9faaf..8f43a3c 100644
--- a/tools/private/release/backport_create_releases.py
+++ b/dev/release/backport_create_releases.py
@@ -4,9 +4,9 @@
 import re
 from dataclasses import dataclass
 
-from tools.private.release.backport_prepare import parse_backport_metadata
-from tools.private.release.gh import GitHub
-from tools.private.release.release_issue import (
+from dev.release.backport_prepare import parse_backport_metadata
+from dev.release.gh import GitHub
+from dev.release.release_issue import (
     add_backports_to_body,
     add_sync_changelog_task_to_body,
     load_release_tracking_template,
diff --git a/tools/private/release/backport_prepare.py b/dev/release/backport_prepare.py
similarity index 97%
rename from tools/private/release/backport_prepare.py
rename to dev/release/backport_prepare.py
index 459e91f..21d00b1 100644
--- a/tools/private/release/backport_prepare.py
+++ b/dev/release/backport_prepare.py
@@ -5,10 +5,10 @@
 import re
 from dataclasses import dataclass
 
-from tools.private.release import changelog_news
-from tools.private.release.gh import BACKPORT_LABEL, GitHub
-from tools.private.release.git import Git
-from tools.private.release.utils import determine_next_version
+from dev.release import changelog_news
+from dev.release.gh import BACKPORT_LABEL, GitHub
+from dev.release.git import Git
+from dev.release.utils import determine_next_version
 
 
 @dataclass
diff --git a/tools/private/release/changelog_news.py b/dev/release/changelog_news.py
similarity index 100%
rename from tools/private/release/changelog_news.py
rename to dev/release/changelog_news.py
diff --git a/tools/private/release/complete_prepare.py b/dev/release/complete_prepare.py
similarity index 95%
rename from tools/private/release/complete_prepare.py
rename to dev/release/complete_prepare.py
index e585a53..a3ba6f0 100644
--- a/tools/private/release/complete_prepare.py
+++ b/dev/release/complete_prepare.py
@@ -2,12 +2,12 @@
 
 import re
 
-from tools.private.release.gh import GitHub
-from tools.private.release.release_issue import (
+from dev.release.gh import GitHub
+from dev.release.release_issue import (
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import set_github_output
+from dev.release.utils import set_github_output
 
 
 class CompletePrepare:
diff --git a/tools/private/release/complete_sync_changelog.py b/dev/release/complete_sync_changelog.py
similarity index 96%
rename from tools/private/release/complete_sync_changelog.py
rename to dev/release/complete_sync_changelog.py
index 9180506..7147a2b 100644
--- a/tools/private/release/complete_sync_changelog.py
+++ b/dev/release/complete_sync_changelog.py
@@ -3,8 +3,8 @@
 import logging
 import re
 
-from tools.private.release.gh import GitHub, get_github_event_pr_number
-from tools.private.release.release_issue import (
+from dev.release.gh import GitHub, get_github_event_pr_number
+from dev.release.release_issue import (
     parse_checklist_state,
     update_task_in_body,
 )
diff --git a/tools/private/release/create_rc.py b/dev/release/create_rc.py
similarity index 96%
rename from tools/private/release/create_rc.py
rename to dev/release/create_rc.py
index 0987bb3..b4e2619 100644
--- a/tools/private/release/create_rc.py
+++ b/dev/release/create_rc.py
@@ -4,17 +4,17 @@
 import traceback
 from argparse import Namespace
 
-from tools.private.release.gh import GH_REACTION_THUMBS_DOWN, GitHub
-from tools.private.release.git import Git
-from tools.private.release.process_backports import ProcessBackports
-from tools.private.release.release_issue import (
+from dev.release.gh import GH_REACTION_THUMBS_DOWN, GitHub
+from dev.release.git import Git
+from dev.release.process_backports import ProcessBackports
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     add_rc_task_to_body,
     parse_backports,
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import (
+from dev.release.utils import (
     REPO_URL,
     get_latest_rc_tag,
     set_github_output,
diff --git a/tools/private/release/create_release_branch.py b/dev/release/create_release_branch.py
similarity index 95%
rename from tools/private/release/create_release_branch.py
rename to dev/release/create_release_branch.py
index 7a9d7ee..aab1903 100644
--- a/tools/private/release/create_release_branch.py
+++ b/dev/release/create_release_branch.py
@@ -1,13 +1,13 @@
 """Subcommand to create a release branch from a merged PR commit."""
 
-from tools.private.release.gh import GitHub
-from tools.private.release.git import Git
-from tools.private.release.release_issue import (
+from dev.release.gh import GitHub
+from dev.release.git import Git
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import REPO_URL
+from dev.release.utils import REPO_URL
 
 
 class CreateReleaseBranch:
diff --git a/tools/private/release/create_release_issue.py b/dev/release/create_release_issue.py
similarity index 89%
rename from tools/private/release/create_release_issue.py
rename to dev/release/create_release_issue.py
index 0777941..1123cfd 100644
--- a/tools/private/release/create_release_issue.py
+++ b/dev/release/create_release_issue.py
@@ -1,8 +1,8 @@
 """Subcommand to create a release tracking issue."""
 
-from tools.private.release.gh import GitHub
-from tools.private.release.release_issue import load_release_tracking_template
-from tools.private.release.utils import determine_next_version, semver_type
+from dev.release.gh import GitHub
+from dev.release.release_issue import load_release_tracking_template
+from dev.release.utils import determine_next_version, semver_type
 
 
 class CreateReleaseIssue:
diff --git a/tools/private/release/determine_next_version.py b/dev/release/determine_next_version.py
similarity index 92%
rename from tools/private/release/determine_next_version.py
rename to dev/release/determine_next_version.py
index 541dadd..be44a6c 100644
--- a/tools/private/release/determine_next_version.py
+++ b/dev/release/determine_next_version.py
@@ -1,6 +1,6 @@
 """Subcommand to determine the next version."""
 
-from tools.private.release.utils import determine_next_version
+from dev.release.utils import determine_next_version
 
 
 class DetermineNextVersion:
diff --git a/tools/private/release/gh.py b/dev/release/gh.py
similarity index 99%
rename from tools/private/release/gh.py
rename to dev/release/gh.py
index 4e7abb9..b3bff9c 100644
--- a/tools/private/release/gh.py
+++ b/dev/release/gh.py
@@ -12,8 +12,8 @@
     override,  # pyrefly: ignore[missing-module-attribute] -- override available in Python 3.12+
 )
 
-from tools.private.release.release_issue import BackportTask
-from tools.private.release.shell import run_cmd
+from dev.release.release_issue import BackportTask
+from dev.release.shell import run_cmd
 
 # GitHub label types
 RELEASE_LABEL = "type: release"
diff --git a/tools/private/release/git.py b/dev/release/git.py
similarity index 99%
rename from tools/private/release/git.py
rename to dev/release/git.py
index 3e7e3cb..bab76a6 100644
--- a/tools/private/release/git.py
+++ b/dev/release/git.py
@@ -2,7 +2,7 @@
 
 import subprocess
 
-from tools.private.release.shell import run_cmd
+from dev.release.shell import run_cmd
 
 
 class Git:
diff --git a/tools/private/release/mock_gh.py b/dev/release/mock_gh.py
similarity index 98%
rename from tools/private/release/mock_gh.py
rename to dev/release/mock_gh.py
index f35d0db..65e19d3 100644
--- a/tools/private/release/mock_gh.py
+++ b/dev/release/mock_gh.py
@@ -5,7 +5,7 @@
     override,  # pyrefly: ignore[missing-module-attribute] -- override available in Python 3.12+
 )
 
-from tools.private.release.gh import (
+from dev.release.gh import (
     RELEASE_LABEL,
     GetPrError,
     GitHubInterface,
@@ -16,7 +16,7 @@
     PrDict,
     resolve_merge_commits_for_prs,
 )
-from tools.private.release.release_issue import BackportTask
+from dev.release.release_issue import BackportTask
 
 
 class MockGitHub(GitHubInterface):
diff --git a/tools/private/release/on_pr_merged.py b/dev/release/on_pr_merged.py
similarity index 93%
rename from tools/private/release/on_pr_merged.py
rename to dev/release/on_pr_merged.py
index 8d45158..de6b0db 100644
--- a/tools/private/release/on_pr_merged.py
+++ b/dev/release/on_pr_merged.py
@@ -3,10 +3,10 @@
 import argparse
 import re
 
-from tools.private.release.gh import GitHub
-from tools.private.release.git import Git
-from tools.private.release.process_backports import ProcessBackports
-from tools.private.release.release_issue import parse_backports
+from dev.release.gh import GitHub
+from dev.release.git import Git
+from dev.release.process_backports import ProcessBackports
+from dev.release.release_issue import parse_backports
 
 
 class OnPrMerged:
diff --git a/tools/private/release/prepare.py b/dev/release/prepare.py
similarity index 97%
rename from tools/private/release/prepare.py
rename to dev/release/prepare.py
index 28783cb..d2df2b0 100644
--- a/tools/private/release/prepare.py
+++ b/dev/release/prepare.py
@@ -3,20 +3,20 @@
 import argparse
 import datetime
 
-from tools.private.release import changelog_news
-from tools.private.release.gh import (
+from dev.release import changelog_news
+from dev.release.gh import (
     RELEASE_PREPARED_LABEL,
     GitHub,
     MultipleTrackingIssuesError,
     NoTrackingIssueError,
 )
-from tools.private.release.git import Git
-from tools.private.release.release_issue import (
+from dev.release.git import Git
+from dev.release.release_issue import (
     load_release_tracking_template,
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import (
+from dev.release.utils import (
     determine_next_version,
     replace_version_next,
     semver_type,
diff --git a/tools/private/release/process_backports.py b/dev/release/process_backports.py
similarity index 98%
rename from tools/private/release/process_backports.py
rename to dev/release/process_backports.py
index 50c6aad..2b2a0aa 100644
--- a/tools/private/release/process_backports.py
+++ b/dev/release/process_backports.py
@@ -7,10 +7,10 @@
 from dataclasses import dataclass
 from typing import Any
 
-from tools.private.release import changelog_news
-from tools.private.release.gh import GH_REACTION_THUMBS_DOWN, GitHub
-from tools.private.release.git import Git
-from tools.private.release.release_issue import (
+from dev.release import changelog_news
+from dev.release.gh import GH_REACTION_THUMBS_DOWN, GitHub
+from dev.release.git import Git
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     add_backports_to_body,
     add_rc_task_to_body,
@@ -19,7 +19,7 @@
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import (
+from dev.release.utils import (
     format_exception,
     get_latest_rc_tag,
     parse_pr_list,
diff --git a/tools/private/release/process_news.py b/dev/release/process_news.py
similarity index 98%
rename from tools/private/release/process_news.py
rename to dev/release/process_news.py
index 6190220..4bce954 100644
--- a/tools/private/release/process_news.py
+++ b/dev/release/process_news.py
@@ -8,14 +8,14 @@
 import re
 import sys
 
-from tools.private.release import changelog_news
-from tools.private.release.gh import (
+from dev.release import changelog_news
+from dev.release.gh import (
     GetPrError,
     GitHub,
     GitHubInterface,
     InvalidPrRefError,
 )
-from tools.private.release.utils import replace_version_next_in_files
+from dev.release.utils import replace_version_next_in_files
 
 logger = logging.getLogger(__name__)
 
diff --git a/tools/private/release/promote.py b/dev/release/promote.py
similarity index 97%
rename from tools/private/release/promote.py
rename to dev/release/promote.py
index 44ccd5b..874a347 100644
--- a/tools/private/release/promote.py
+++ b/dev/release/promote.py
@@ -4,13 +4,13 @@
 import os
 import urllib.parse
 
-from tools.private.release.gh import GitHub
-from tools.private.release.git import Git
-from tools.private.release.release_issue import (
+from dev.release.gh import GitHub
+from dev.release.git import Git
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     update_task_in_body,
 )
-from tools.private.release.utils import (
+from dev.release.utils import (
     REPO_URL,
     determine_next_version,
     get_latest_rc_tag,
diff --git a/tools/private/release/release.py b/dev/release/release.py
similarity index 68%
rename from tools/private/release/release.py
rename to dev/release/release.py
index 64fd118..501ff2c 100644
--- a/tools/private/release/release.py
+++ b/dev/release/release.py
@@ -5,22 +5,22 @@
 import os
 import sys
 
-from tools.private.release.add_backports import AddBackports
-from tools.private.release.backport_create_releases import BackportCreateReleases
-from tools.private.release.backport_prepare import BackportPrepare
-from tools.private.release.complete_prepare import CompletePrepare
-from tools.private.release.complete_sync_changelog import CompleteSyncChangelog
-from tools.private.release.create_rc import CreateRc
-from tools.private.release.create_release_branch import CreateReleaseBranch
-from tools.private.release.create_release_issue import CreateReleaseIssue
-from tools.private.release.determine_next_version import DetermineNextVersion
-from tools.private.release.on_pr_merged import OnPrMerged
-from tools.private.release.prepare import Prepare
-from tools.private.release.process_backports import ProcessBackports
-from tools.private.release.process_news import ProcessNews
-from tools.private.release.promote import Promote
-from tools.private.release.sync_changelog import SyncChangelog
-from tools.private.release.utils import format_exception
+from dev.release.add_backports import AddBackports
+from dev.release.backport_create_releases import BackportCreateReleases
+from dev.release.backport_prepare import BackportPrepare
+from dev.release.complete_prepare import CompletePrepare
+from dev.release.complete_sync_changelog import CompleteSyncChangelog
+from dev.release.create_rc import CreateRc
+from dev.release.create_release_branch import CreateReleaseBranch
+from dev.release.create_release_issue import CreateReleaseIssue
+from dev.release.determine_next_version import DetermineNextVersion
+from dev.release.on_pr_merged import OnPrMerged
+from dev.release.prepare import Prepare
+from dev.release.process_backports import ProcessBackports
+from dev.release.process_news import ProcessNews
+from dev.release.promote import Promote
+from dev.release.sync_changelog import SyncChangelog
+from dev.release.utils import format_exception
 
 cmds = [
     DetermineNextVersion,
diff --git a/tools/private/release/release_issue.py b/dev/release/release_issue.py
similarity index 100%
rename from tools/private/release/release_issue.py
rename to dev/release/release_issue.py
diff --git a/tools/private/release/shell.py b/dev/release/shell.py
similarity index 100%
rename from tools/private/release/shell.py
rename to dev/release/shell.py
diff --git a/tools/private/release/sync_changelog.py b/dev/release/sync_changelog.py
similarity index 97%
rename from tools/private/release/sync_changelog.py
rename to dev/release/sync_changelog.py
index c7e3d38..229bf90 100644
--- a/tools/private/release/sync_changelog.py
+++ b/dev/release/sync_changelog.py
@@ -6,20 +6,20 @@
 import os
 import traceback
 
-from tools.private.release.gh import (
+from dev.release.gh import (
     SYNC_CHANGELOG_LABEL,
     GitHub,
     GitHubInterface,
     get_github_event_issue_number,
 )
-from tools.private.release.git import Git
-from tools.private.release.process_news import ProcessNews
-from tools.private.release.release_issue import (
+from dev.release.git import Git
+from dev.release.process_news import ProcessNews
+from dev.release.release_issue import (
     RELEASE_TITLE_RE,
     parse_checklist_state,
     update_task_in_body,
 )
-from tools.private.release.utils import (
+from dev.release.utils import (
     format_exception,
     parse_pr_list,
 )
diff --git a/tools/private/release/utils.py b/dev/release/utils.py
similarity index 98%
rename from tools/private/release/utils.py
rename to dev/release/utils.py
index f6e266f..407044a 100644
--- a/tools/private/release/utils.py
+++ b/dev/release/utils.py
@@ -9,7 +9,7 @@
 
 from packaging.version import parse as parse_version
 
-from tools.private.release.git import Git
+from dev.release.git import Git
 
 REPO_URL = "https://github.com/bazel-contrib/rules_python"
 
@@ -32,7 +32,7 @@
     "./bazel-*/*",
     "./CONTRIBUTING.md",
     "./RELEASING.md",
-    "./tools/private/release/*",
+    "./dev/release/*",
     "./tests/tools/private/release/*",
 ]
 
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
index fd52473..79653a6 100644
--- a/docs/BUILD.bazel
+++ b/docs/BUILD.bazel
@@ -42,7 +42,7 @@
     srcs = ["merge_changelog.py"],
     target_compatible_with = _TARGET_COMPATIBLE_WITH,
     deps = [
-        "//tools/private/release:changelog_news",
+        "//dev/release:changelog_news",
     ],
 )
 
diff --git a/docs/devguide.md b/docs/devguide.md
index 96cf5cd..22839bc 100644
--- a/docs/devguide.md
+++ b/docs/devguide.md
@@ -134,7 +134,7 @@
 4.  Update the release's `CHANGELOG.md` file using the release tool's
     `process-news` command:
     ```shell
-    bazel run //tools/private/release -- process-news <VERSION> <PR_NUMBER>
+    bazel run //dev/release -- process-news <VERSION> <PR_NUMBER>
     ```
     This merges the PR's news entries into `CHANGELOG.md`, deletes the news
     files, and updates any `VERSION_NEXT_*` markers.
@@ -158,7 +158,7 @@
     * Checkout the `main` branch.
     * Run the `process-news` command as before:
       ```shell
-      bazel run //tools/private/release -- process-news <VERSION> <PR_NUMBER>
+      bazel run //dev/release -- process-news <VERSION> <PR_NUMBER>
       ```
 
 [backport-issue]: https://github.com/bazel-contrib/rules_python/issues/new?template=release_tracking_template.md
diff --git a/docs/merge_changelog.py b/docs/merge_changelog.py
index 47af98b..d515ddb 100644
--- a/docs/merge_changelog.py
+++ b/docs/merge_changelog.py
@@ -3,7 +3,7 @@
 import argparse
 import pathlib
 
-from tools.private.release import changelog_news
+from dev.release import changelog_news
 
 
 def main():
diff --git a/tests/tools/private/release/BUILD.bazel b/tests/tools/private/release/BUILD.bazel
index 4f94c9b..bc395a7 100644
--- a/tests/tools/private/release/BUILD.bazel
+++ b/tests/tools/private/release/BUILD.bazel
@@ -9,7 +9,7 @@
     target_compatible_with = NOT_WINDOWS,
     deps = [
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
         "@pypi//pytest_mock",
     ],
 )
@@ -21,9 +21,9 @@
     data = ["//.github/ISSUE_TEMPLATE:release_tracking_template.md"],
     target_compatible_with = NOT_WINDOWS,
     deps = [
+        "//dev/release:mock_gh",
+        "//dev/release:release_lib",
         "//python/runfiles",
-        "//tools/private/release:mock_gh",
-        "//tools/private/release:release_lib",
         "@pypi//pytest",
     ],
 )
@@ -36,7 +36,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -48,7 +48,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -60,7 +60,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -72,7 +72,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -84,7 +84,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -96,7 +96,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -108,7 +108,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -120,7 +120,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -132,7 +132,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -144,7 +144,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -156,7 +156,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -168,7 +168,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -180,7 +180,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -192,7 +192,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -204,7 +204,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -216,7 +216,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -228,7 +228,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -240,7 +240,7 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
 
@@ -252,6 +252,6 @@
     deps = [
         ":conftest",
         ":release_test_helper",
-        "//tools/private/release:release_lib",
+        "//dev/release:release_lib",
     ],
 )
diff --git a/tests/tools/private/release/add_backports_test.py b/tests/tools/private/release/add_backports_test.py
index 5a33d6d..08fd9a8 100644
--- a/tests/tools/private/release/add_backports_test.py
+++ b/tests/tools/private/release/add_backports_test.py
@@ -1,6 +1,6 @@
 import argparse
 
-from tools.private.release.add_backports import AddBackports
+from dev.release.add_backports import AddBackports
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
diff --git a/tests/tools/private/release/backport_create_releases_test.py b/tests/tools/private/release/backport_create_releases_test.py
index 1842a75..e3ddbcc 100644
--- a/tests/tools/private/release/backport_create_releases_test.py
+++ b/tests/tools/private/release/backport_create_releases_test.py
@@ -1,6 +1,6 @@
 import argparse
 
-from tools.private.release.backport_create_releases import BackportCreateReleases
+from dev.release.backport_create_releases import BackportCreateReleases
 
 # Register pytest fixtures (such as release_tool_env) from release_test_helper
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
diff --git a/tests/tools/private/release/backport_prepare_test.py b/tests/tools/private/release/backport_prepare_test.py
index ddef488..99fac9d 100644
--- a/tests/tools/private/release/backport_prepare_test.py
+++ b/tests/tools/private/release/backport_prepare_test.py
@@ -1,8 +1,8 @@
 import argparse
 from unittest.mock import ANY, call
 
-from tools.private.release.backport_prepare import BackportPrepare
-from tools.private.release.gh import BACKPORT_LABEL
+from dev.release.backport_prepare import BackportPrepare
+from dev.release.gh import BACKPORT_LABEL
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -45,10 +45,8 @@
     ]
     mock_git.get_current_branch.return_value = "work-branch"
 
-    mock_news = mocker.patch("tools.private.release.backport_prepare.changelog_news")
-    mock_det = mocker.patch(
-        "tools.private.release.backport_prepare.determine_next_version"
-    )
+    mock_news = mocker.patch("dev.release.backport_prepare.changelog_news")
+    mock_det = mocker.patch("dev.release.backport_prepare.determine_next_version")
     mock_det.side_effect = ["1.7.2", "1.8.1", "1.9.0"]
 
     # Act
@@ -123,10 +121,8 @@
     ]
     mock_git.get_current_branch.return_value = "work-branch"
 
-    mocker.patch("tools.private.release.backport_prepare.changelog_news")
-    mock_det = mocker.patch(
-        "tools.private.release.backport_prepare.determine_next_version"
-    )
+    mocker.patch("dev.release.backport_prepare.changelog_news")
+    mock_det = mocker.patch("dev.release.backport_prepare.determine_next_version")
     mock_det.side_effect = ["1.7.2", "1.8.1"]
 
     # Act
@@ -165,10 +161,8 @@
     ]
     mock_git.get_current_branch.return_value = "work-branch"
 
-    mocker.patch("tools.private.release.backport_prepare.changelog_news")
-    mock_det = mocker.patch(
-        "tools.private.release.backport_prepare.determine_next_version"
-    )
+    mocker.patch("dev.release.backport_prepare.changelog_news")
+    mock_det = mocker.patch("dev.release.backport_prepare.determine_next_version")
     mock_det.side_effect = ["1.7.2", "1.8.1"]
 
     # Act
@@ -210,10 +204,8 @@
     ]
     mock_git.get_current_branch.return_value = "work-branch"
 
-    mock_news = mocker.patch("tools.private.release.backport_prepare.changelog_news")
-    mock_det = mocker.patch(
-        "tools.private.release.backport_prepare.determine_next_version"
-    )
+    mock_news = mocker.patch("dev.release.backport_prepare.changelog_news")
+    mock_det = mocker.patch("dev.release.backport_prepare.determine_next_version")
     mock_det.side_effect = ["1.7.2", "1.8.1"]
     mock_git.cherry_pick.side_effect = [Exception("Conflict"), None]
     mock_news.update_changelog.side_effect = [Exception("Changelog error")]
diff --git a/tests/tools/private/release/changelog_news_test.py b/tests/tools/private/release/changelog_news_test.py
index dd2bd6d..c251efc 100644
--- a/tests/tools/private/release/changelog_news_test.py
+++ b/tests/tools/private/release/changelog_news_test.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from tools.private.release import changelog_news
+from dev.release import changelog_news
 
 
 def test_update_changelog_with_news(tmp_path):
diff --git a/tests/tools/private/release/complete_prepare_test.py b/tests/tools/private/release/complete_prepare_test.py
index b710c0d..e475961 100644
--- a/tests/tools/private/release/complete_prepare_test.py
+++ b/tests/tools/private/release/complete_prepare_test.py
@@ -1,6 +1,6 @@
 import argparse
 
-from tools.private.release.complete_prepare import CompletePrepare
+from dev.release.complete_prepare import CompletePrepare
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
diff --git a/tests/tools/private/release/complete_sync_changelog_test.py b/tests/tools/private/release/complete_sync_changelog_test.py
index 6948dc5..097ecd4 100644
--- a/tests/tools/private/release/complete_sync_changelog_test.py
+++ b/tests/tools/private/release/complete_sync_changelog_test.py
@@ -1,6 +1,6 @@
 import argparse
 
-from tools.private.release.complete_sync_changelog import CompleteSyncChangelog
+from dev.release.complete_sync_changelog import CompleteSyncChangelog
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
diff --git a/tests/tools/private/release/conftest.py b/tests/tools/private/release/conftest.py
index f8798c1..759022f 100644
--- a/tests/tools/private/release/conftest.py
+++ b/tests/tools/private/release/conftest.py
@@ -18,22 +18,22 @@
 @pytest.fixture(name="mock_run_cmd")
 def fixture_mock_run_cmd(mocker):
     """Fixture to patch shell.run_cmd and its imports in git and gh modules."""
-    mock = mocker.patch("tools.private.release.shell.run_cmd")
-    mocker.patch("tools.private.release.git.run_cmd", mock)
-    mocker.patch("tools.private.release.gh.run_cmd", mock)
+    mock = mocker.patch("dev.release.shell.run_cmd")
+    mocker.patch("dev.release.git.run_cmd", mock)
+    mocker.patch("dev.release.gh.run_cmd", mock)
     return mock
 
 
 @pytest.fixture(name="mock_run_git")
 def fixture_mock_run_git(mocker):
     """Fixture to patch Git._run_git."""
-    return mocker.patch("tools.private.release.git.Git._run_git")
+    return mocker.patch("dev.release.git.Git._run_git")
 
 
 @pytest.fixture(name="mock_run_gh")
 def fixture_mock_run_gh(mocker):
     """Fixture to patch GitHub._run_gh."""
-    return mocker.patch("tools.private.release.gh.GitHub._run_gh")
+    return mocker.patch("dev.release.gh.GitHub._run_gh")
 
 
 @pytest.fixture(name="auto_patch_cmd_helpers", autouse=True)
diff --git a/tests/tools/private/release/create_rc_test.py b/tests/tools/private/release/create_rc_test.py
index 6aee746..f5b8f1c 100644
--- a/tests/tools/private/release/create_rc_test.py
+++ b/tests/tools/private/release/create_rc_test.py
@@ -4,7 +4,7 @@
 import tempfile
 from unittest.mock import call
 
-from tools.private.release.create_rc import CreateRc
+from dev.release.create_rc import CreateRc
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -271,7 +271,7 @@
 
 def test_create_rc_calls_process_backports(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 0
 
@@ -308,7 +308,7 @@
 
 def test_create_rc_aborts_on_process_backports_failure(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 1
 
@@ -328,7 +328,7 @@
 
 def test_create_rc_failure_reacts_to_comment(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 1  # Simulate failure
 
@@ -346,7 +346,7 @@
 
 def test_create_rc_failure_no_comment_no_reaction(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 1  # Simulate failure
 
@@ -364,7 +364,7 @@
 
 def test_create_rc_success_with_comment_no_reaction(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 0
 
@@ -394,7 +394,7 @@
 
 def test_create_rc_precondition_failure_reacts_to_comment(mocker, mock_git, mock_gh):
     # Arrange
-    mock_pb_class = mocker.patch("tools.private.release.create_rc.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.create_rc.ProcessBackports")
     mock_pb = mock_pb_class.return_value
     mock_pb.run.return_value = 0  # Backports succeed
 
diff --git a/tests/tools/private/release/create_release_branch_test.py b/tests/tools/private/release/create_release_branch_test.py
index 332ffec..5318b28 100644
--- a/tests/tools/private/release/create_release_branch_test.py
+++ b/tests/tools/private/release/create_release_branch_test.py
@@ -1,6 +1,6 @@
 import argparse
 
-from tools.private.release.create_release_branch import CreateReleaseBranch
+from dev.release.create_release_branch import CreateReleaseBranch
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
diff --git a/tests/tools/private/release/gh_test.py b/tests/tools/private/release/gh_test.py
index 19f8658..e1aea0e 100644
--- a/tests/tools/private/release/gh_test.py
+++ b/tests/tools/private/release/gh_test.py
@@ -2,14 +2,14 @@
 
 import pytest
 
-from tools.private.release import shell
-from tools.private.release.gh import (
+from dev.release import shell
+from dev.release.gh import (
     CreatePrError,
     GetPrError,
     GitHub,
     InvalidPrRefError,
 )
-from tools.private.release.git import Git
+from dev.release.git import Git
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -20,7 +20,7 @@
 
 
 def test_resolve_pr_number_digit(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     # 124 and #125 should resolve immediately without running command
     assert gh.resolve_pr_number("124") == 124
     assert gh.resolve_pr_number("#125") == 125
@@ -28,7 +28,7 @@
 
 
 def test_resolve_pr_number_url_simple(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     url = "https://github.com/my-owner/my-repo/pull/126"
     # Should resolve via regex without calling gh
     result = gh.resolve_pr_number(url)
@@ -37,7 +37,7 @@
 
 
 def test_resolve_pr_number_url_with_subpath(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     url = "https://github.com/my-owner/my-repo/pull/126/files"
     # Should resolve via regex without calling gh
     result = gh.resolve_pr_number(url)
@@ -46,7 +46,7 @@
 
 
 def test_resolve_pr_number_url_with_query(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     url = "https://github.com/my-owner/my-repo/pull/126/files?w=1"
     # Should resolve via regex without calling gh
     result = gh.resolve_pr_number(url)
@@ -55,7 +55,7 @@
 
 
 def test_resolve_pr_number_url_other_repo(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     # URL for a different repo should fail immediately without calling gh
     url = "https://github.com/other-owner/other-repo/pull/126"
     with pytest.raises(
@@ -66,7 +66,7 @@
 
 
 def test_resolve_pr_number_invalid(mocker, gh):
-    mock_run_cmd = mocker.patch("tools.private.release.gh.run_cmd")
+    mock_run_cmd = mocker.patch("dev.release.gh.run_cmd")
     with pytest.raises(InvalidPrRefError, match="Could not resolve PR reference"):
         gh.resolve_pr_number("invalid-ref")
     mock_run_cmd.assert_not_called()
diff --git a/tests/tools/private/release/git_test.py b/tests/tools/private/release/git_test.py
index e39787f..b9ed755 100644
--- a/tests/tools/private/release/git_test.py
+++ b/tests/tools/private/release/git_test.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from tools.private.release.git import Git
+from dev.release.git import Git
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -23,7 +23,7 @@
 
 def test_checkout_track_remote_new_branch(mocker, git_obj):
     mock_branch_exists = mocker.patch(
-        "tools.private.release.git.Git.branch_exists", return_value=False
+        "dev.release.git.Git.branch_exists", return_value=False
     )
 
     git_obj.checkout("my-branch", track_remote="origin")
@@ -36,9 +36,9 @@
 
 def test_checkout_track_remote_existing_branch(mocker, git_obj):
     mock_branch_exists = mocker.patch(
-        "tools.private.release.git.Git.branch_exists", return_value=True
+        "dev.release.git.Git.branch_exists", return_value=True
     )
-    mock_reset_hard = mocker.patch("tools.private.release.git.Git.reset_hard")
+    mock_reset_hard = mocker.patch("dev.release.git.Git.reset_hard")
 
     git_obj.checkout("my-branch", track_remote="origin")
 
diff --git a/tests/tools/private/release/on_pr_merged_test.py b/tests/tools/private/release/on_pr_merged_test.py
index df3698d..1e9c0a1 100644
--- a/tests/tools/private/release/on_pr_merged_test.py
+++ b/tests/tools/private/release/on_pr_merged_test.py
@@ -1,7 +1,7 @@
 import argparse
 from unittest.mock import MagicMock
 
-from tools.private.release.on_pr_merged import OnPrMerged
+from dev.release.on_pr_merged import OnPrMerged
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -15,7 +15,7 @@
         ]
     }
 
-    mock_pb = mocker.patch("tools.private.release.on_pr_merged.ProcessBackports")
+    mock_pb = mocker.patch("dev.release.on_pr_merged.ProcessBackports")
     result = OnPrMerged(args, mock_git, mock_gh).run()
 
     assert result == 1
@@ -26,7 +26,7 @@
     args = argparse.Namespace(pr=124, remote="origin", dry_run=True)
     mock_gh.pr_comments = {124: [{"body": "/backport"}]}
 
-    mock_pb = mocker.patch("tools.private.release.on_pr_merged.ProcessBackports")
+    mock_pb = mocker.patch("dev.release.on_pr_merged.ProcessBackports")
     result = OnPrMerged(args, mock_git, mock_gh).run()
 
     assert result == 1
@@ -48,7 +48,7 @@
         labels=["type: release"],
     )
 
-    mock_pb = mocker.patch("tools.private.release.on_pr_merged.ProcessBackports")
+    mock_pb = mocker.patch("dev.release.on_pr_merged.ProcessBackports")
     result = OnPrMerged(args, mock_git, mock_gh).run()
 
     assert result == 1
@@ -70,7 +70,7 @@
         labels=["type: release"],
     )
 
-    mock_pb_class = mocker.patch("tools.private.release.on_pr_merged.ProcessBackports")
+    mock_pb_class = mocker.patch("dev.release.on_pr_merged.ProcessBackports")
     mock_pb_instance = MagicMock()
     mock_pb_instance.run.return_value = 0
     mock_pb_class.return_value = mock_pb_instance
diff --git a/tests/tools/private/release/prepare_test.py b/tests/tools/private/release/prepare_test.py
index 64275df..62d2b62 100644
--- a/tests/tools/private/release/prepare_test.py
+++ b/tests/tools/private/release/prepare_test.py
@@ -1,14 +1,14 @@
 import argparse
 
-from tools.private.release.gh import RELEASE_PREPARED_LABEL
-from tools.private.release.prepare import Prepare
+from dev.release.gh import RELEASE_PREPARED_LABEL
+from dev.release.prepare import Prepare
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
 
 def test_prepare_success_existing_issue(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -33,8 +33,8 @@
 
 
 def test_prepare_success_create_issue(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange: release_tool_env sets up template_file automatically
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -56,8 +56,8 @@
 
 
 def test_prepare_ambiguous_issue(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -79,8 +79,8 @@
 
 
 def test_prepare_dry_run(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=True)
@@ -102,8 +102,8 @@
 def test_prepare_use_associated_pr_from_tracking_issue(
     mocker, release_tool_env, mock_git, mock_gh
 ):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -132,8 +132,8 @@
 def test_prepare_create_pr_when_none_associated(
     mocker, release_tool_env, mock_git, mock_gh
 ):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -161,8 +161,8 @@
 
 
 def test_prepare_reuse_existing_pr(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=False)
@@ -195,8 +195,8 @@
 
 
 def test_prepare_dry_run_no_issue(mocker, release_tool_env, mock_git, mock_gh):
-    mocker.patch("tools.private.release.prepare.replace_version_next")
-    mocker.patch("tools.private.release.prepare.changelog_news")
+    mocker.patch("dev.release.prepare.replace_version_next")
+    mocker.patch("dev.release.prepare.changelog_news")
 
     # Arrange
     args = argparse.Namespace(version="2.0.0", issue=None, dry_run=True)
diff --git a/tests/tools/private/release/process_backports_test.py b/tests/tools/private/release/process_backports_test.py
index 1acc93a..8733573 100644
--- a/tests/tools/private/release/process_backports_test.py
+++ b/tests/tools/private/release/process_backports_test.py
@@ -3,7 +3,7 @@
 import logging
 from unittest.mock import call
 
-from tools.private.release.process_backports import ProcessBackports
+from dev.release.process_backports import ProcessBackports
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -25,13 +25,9 @@
 
 
 def test_process_backports_success(mocker, mock_git, mock_gh):
-    mock_changelog = mocker.patch(
-        "tools.private.release.process_backports.changelog_news"
-    )
-    mock_replace = mocker.patch(
-        "tools.private.release.process_backports.replace_version_next"
-    )
-    mock_datetime = mocker.patch("tools.private.release.process_backports.datetime")
+    mock_changelog = mocker.patch("dev.release.process_backports.changelog_news")
+    mock_replace = mocker.patch("dev.release.process_backports.replace_version_next")
+    mock_datetime = mocker.patch("dev.release.process_backports.datetime")
     mock_datetime.date.today.return_value = datetime.date(2026, 7, 1)
 
     args = argparse.Namespace(
@@ -86,9 +82,9 @@
 
 
 def test_process_backports_dry_run(mocker, mock_git, mock_gh):
-    mocker.patch("tools.private.release.process_backports.changelog_news")
-    mocker.patch("tools.private.release.process_backports.replace_version_next")
-    mock_datetime = mocker.patch("tools.private.release.process_backports.datetime")
+    mocker.patch("dev.release.process_backports.changelog_news")
+    mocker.patch("dev.release.process_backports.replace_version_next")
+    mock_datetime = mocker.patch("dev.release.process_backports.datetime")
     mock_datetime.date.today.return_value = datetime.date(2026, 7, 1)
 
     args = argparse.Namespace(
@@ -182,7 +178,7 @@
 
 
 def test_process_backports_cherry_pick_failed(mocker, mock_git, mock_gh):
-    mock_datetime = mocker.patch("tools.private.release.process_backports.datetime")
+    mock_datetime = mocker.patch("dev.release.process_backports.datetime")
     mock_datetime.date.today.return_value = datetime.date(2026, 7, 1)
     args = argparse.Namespace(
         issue=123, remote="origin", dry_run=False, add=None, triggering_comment=None
@@ -223,9 +219,9 @@
 def test_process_backports_add_backports_and_auto_add_rc_task(
     mocker, mock_git, mock_gh
 ):
-    mocker.patch("tools.private.release.process_backports.changelog_news")
-    mocker.patch("tools.private.release.process_backports.replace_version_next")
-    mock_datetime = mocker.patch("tools.private.release.process_backports.datetime")
+    mocker.patch("dev.release.process_backports.changelog_news")
+    mocker.patch("dev.release.process_backports.replace_version_next")
+    mock_datetime = mocker.patch("dev.release.process_backports.datetime")
     mock_datetime.date.today.return_value = datetime.date(2026, 7, 1)
     args = argparse.Namespace(
         issue=123,
@@ -265,9 +261,9 @@
 
 
 def test_process_backports_add_backports_marks_invalid(mocker, mock_git, mock_gh):
-    mocker.patch("tools.private.release.process_backports.changelog_news")
-    mocker.patch("tools.private.release.process_backports.replace_version_next")
-    mock_datetime = mocker.patch("tools.private.release.process_backports.datetime")
+    mocker.patch("dev.release.process_backports.changelog_news")
+    mocker.patch("dev.release.process_backports.replace_version_next")
+    mock_datetime = mocker.patch("dev.release.process_backports.datetime")
     mock_datetime.date.today.return_value = datetime.date(2026, 7, 1)
     args = argparse.Namespace(
         issue=123,
diff --git a/tests/tools/private/release/process_news_test.py b/tests/tools/private/release/process_news_test.py
index 2aa6131..d6bfd31 100644
--- a/tests/tools/private/release/process_news_test.py
+++ b/tests/tools/private/release/process_news_test.py
@@ -1,7 +1,7 @@
 import argparse
 
-from tools.private.release.process_news import ProcessNews
-from tools.private.release.release import create_parser
+from dev.release.process_news import ProcessNews
+from dev.release.release import create_parser
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -215,13 +215,13 @@
 
     processed_order = []
     mocker.patch(
-        "tools.private.release.process_news.process_pr_target",
+        "dev.release.process_news.process_pr_target",
         side_effect=lambda target, ver, p, **kwargs: processed_order.append(
             f"PR:{target.pr_num}"
         ),
     )
     mocker.patch(
-        "tools.private.release.process_news.process_news_file_target",
+        "dev.release.process_news.process_news_file_target",
         side_effect=lambda target, ver, p, **kwargs: processed_order.append(
             f"FILE:{target.path.name}"
         ),
diff --git a/tests/tools/private/release/promote_test.py b/tests/tools/private/release/promote_test.py
index b7fe9c9..e1c737a 100644
--- a/tests/tools/private/release/promote_test.py
+++ b/tests/tools/private/release/promote_test.py
@@ -2,7 +2,7 @@
 from pathlib import Path
 from unittest.mock import call, patch
 
-from tools.private.release.promote import Promote
+from dev.release.promote import Promote
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
diff --git a/tests/tools/private/release/release_issue_test.py b/tests/tools/private/release/release_issue_test.py
index 13a543e..3530dfd 100644
--- a/tests/tools/private/release/release_issue_test.py
+++ b/tests/tools/private/release/release_issue_test.py
@@ -1,4 +1,4 @@
-from tools.private.release.release_issue import (
+from dev.release.release_issue import (
     add_backports_to_body,
     add_sync_changelog_task_to_body,
     format_metadata_line,
diff --git a/tests/tools/private/release/release_test.py b/tests/tools/private/release/release_test.py
index f383c85..1a12834 100644
--- a/tests/tools/private/release/release_test.py
+++ b/tests/tools/private/release/release_test.py
@@ -1,6 +1,6 @@
 import pytest
 
-from tools.private.release import release as releaser
+from dev.release import release as releaser
 
 
 def test_valid_version():
@@ -21,9 +21,7 @@
 
 def test_main_runs_command(mocker):
     mocker.patch("sys.argv", ["release", "prepare", "0.28.0"])
-    mock_cmd = mocker.patch(
-        "tools.private.release.prepare.Prepare.run_from_args", return_value=0
-    )
+    mock_cmd = mocker.patch("dev.release.prepare.Prepare.run_from_args", return_value=0)
     with pytest.raises(SystemExit) as exc_info:
         releaser.main()
     assert exc_info.value.code == 0
diff --git a/tests/tools/private/release/release_test_helper.py b/tests/tools/private/release/release_test_helper.py
index b7abd5e..54c1085 100644
--- a/tests/tools/private/release/release_test_helper.py
+++ b/tests/tools/private/release/release_test_helper.py
@@ -6,8 +6,8 @@
 
 import pytest
 
+from dev.release.mock_gh import MockGitHub
 from python.runfiles import runfiles
-from tools.private.release.mock_gh import MockGitHub
 
 
 @dataclasses.dataclass
@@ -97,7 +97,7 @@
     mock_git_inst.branch_exists.return_value = False
     mock_git_inst.tag_exists.return_value = False
 
-    with patch("tools.private.release.utils.Git", return_value=mock_git_inst):
+    with patch("dev.release.utils.Git", return_value=mock_git_inst):
         yield mock_git_inst
 
 
diff --git a/tests/tools/private/release/sync_changelog_test.py b/tests/tools/private/release/sync_changelog_test.py
index 3db4dca..ee438a6 100644
--- a/tests/tools/private/release/sync_changelog_test.py
+++ b/tests/tools/private/release/sync_changelog_test.py
@@ -1,9 +1,9 @@
 import argparse
 from unittest.mock import MagicMock, call
 
-from tools.private.release.gh import CreatePrError
-from tools.private.release.release import create_parser
-from tools.private.release.sync_changelog import SyncChangelog
+from dev.release.gh import CreatePrError
+from dev.release.release import create_parser
+from dev.release.sync_changelog import SyncChangelog
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
@@ -31,9 +31,7 @@
 
 
 def test_sync_changelog_success(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -100,9 +98,7 @@
 
 
 def test_sync_changelog_from_github_event_path(mocker, mock_git, mock_gh, gha):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -135,9 +131,7 @@
 
 
 def test_sync_changelog_branch_exists(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -174,9 +168,7 @@
 
 
 def test_sync_changelog_auto_discover_issue(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -235,9 +227,7 @@
 
 
 def test_sync_changelog_specific_prs_arg(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -267,9 +257,7 @@
 
 
 def test_sync_changelog_no_changes(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
@@ -299,9 +287,7 @@
 
 
 def test_sync_changelog_process_news_failure(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 1
     mock_process_news_class.return_value = mock_process_news_instance
@@ -334,9 +320,7 @@
 
 
 def test_sync_changelog_create_pr_failure(mocker, mock_git, mock_gh):
-    mock_process_news_class = mocker.patch(
-        "tools.private.release.sync_changelog.ProcessNews"
-    )
+    mock_process_news_class = mocker.patch("dev.release.sync_changelog.ProcessNews")
     mock_process_news_instance = MagicMock()
     mock_process_news_instance.run.return_value = 0
     mock_process_news_class.return_value = mock_process_news_instance
diff --git a/tests/tools/private/release/utils_test.py b/tests/tools/private/release/utils_test.py
index 5eeac65..accae63 100644
--- a/tests/tools/private/release/utils_test.py
+++ b/tests/tools/private/release/utils_test.py
@@ -1,13 +1,13 @@
 import pytest
 
-from tools.private.release import utils
+from dev.release import utils
 
 pytest_plugins = ["tests.tools.private.release.release_test_helper"]
 
 
 def test_get_latest_version_success(mocker):
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["0.1.0", "1.0.0", "0.2.0"],
     )
     assert utils.get_latest_version() == "1.0.0"
@@ -15,7 +15,7 @@
 
 def test_get_latest_version_rc_is_latest(mocker):
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["0.1.0", "1.0.0", "1.1.0rc0"],
     )
     with pytest.raises(
@@ -25,7 +25,7 @@
 
 
 def test_get_latest_version_no_tags(mocker):
-    mocker.patch("tools.private.release.git.Git.get_tags", return_value=[])
+    mocker.patch("dev.release.git.Git.get_tags", return_value=[])
     with pytest.raises(
         RuntimeError, match="No git tags found matching X.Y.Z or X.Y.ZrcN format."
     ):
@@ -33,9 +33,7 @@
 
 
 def test_get_latest_version_no_matching_tags(mocker):
-    mocker.patch(
-        "tools.private.release.git.Git.get_tags", return_value=["v1.0", "latest"]
-    )
+    mocker.patch("dev.release.git.Git.get_tags", return_value=["v1.0", "latest"])
     with pytest.raises(
         RuntimeError, match="No git tags found matching X.Y.Z or X.Y.ZrcN format."
     ):
@@ -43,9 +41,7 @@
 
 
 def test_get_latest_version_only_rc_tags(mocker):
-    mocker.patch(
-        "tools.private.release.git.Git.get_tags", return_value=["1.0.0rc0", "1.1.0rc0"]
-    )
+    mocker.patch("dev.release.git.Git.get_tags", return_value=["1.0.0rc0", "1.1.0rc0"])
     with pytest.raises(
         ValueError, match="The latest version is a pre-release version: 1.1.0rc0"
     ):
@@ -53,13 +49,13 @@
 
 
 def test_get_latest_rc_tag_no_tags(mocker):
-    mocker.patch("tools.private.release.git.Git.get_tags", return_value=[])
+    mocker.patch("dev.release.git.Git.get_tags", return_value=[])
     assert utils.get_latest_rc_tag("2.0.0") is None
 
 
 def test_get_latest_rc_tag_no_matching_tags(mocker):
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=[
             "1.0.0",
             "2.0.0",
@@ -72,7 +68,7 @@
 
 def test_get_latest_rc_tag_success(mocker):
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=[
             "2.0.0-rc0",
             "2.0.0-rc2",
@@ -85,7 +81,7 @@
 
 def test_get_latest_rc_tag_ignores_v_prefix(mocker):
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["v2.0.0-rc0", "2.0.0-rc1"],
     )
     assert utils.get_latest_rc_tag("2.0.0") == "2.0.0-rc1"
@@ -93,7 +89,7 @@
 
 def test_get_latest_rc_tag_remote_success(mocker):
     mock_get_remote_tags = mocker.patch(
-        "tools.private.release.git.Git.get_remote_tags",
+        "dev.release.git.Git.get_remote_tags",
         return_value=[
             "2.0.0-rc0",
             "2.0.0-rc2",
@@ -106,10 +102,8 @@
 
 
 def test_determine_next_version_no_markers(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file.bzl").write_text("no markers here")
 
     next_version = utils.determine_next_version()
@@ -118,10 +112,8 @@
 
 
 def test_determine_next_version_only_patch(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file.bzl").write_text(
         ":::{versionchanged} VERSION_NEXT_PATCH"
     )
@@ -132,10 +124,8 @@
 
 
 def test_determine_next_version_only_feature(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file.bzl").write_text(
         ":::{versionadded} VERSION_NEXT_FEATURE"
     )
@@ -146,10 +136,8 @@
 
 
 def test_determine_next_version_both_markers(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file_patch.bzl").write_text(
         ":::{versionchanged} VERSION_NEXT_PATCH"
     )
@@ -163,11 +151,9 @@
 
 
 def test_determine_next_version_on_release_branch_with_existing_tags(mocker):
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="release/0.37")
     mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="release/0.37"
-    )
-    mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["0.37.0", "0.37.1", "0.36.0"],
     )
 
@@ -177,11 +163,9 @@
 
 
 def test_determine_next_version_on_release_branch_no_tags(mocker):
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="release/0.38")
     mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="release/0.38"
-    )
-    mocker.patch(
-        "tools.private.release.git.Git.get_tags", return_value=["0.37.0"]
+        "dev.release.git.Git.get_tags", return_value=["0.37.0"]
     )  # No 0.38.x tags
 
     next_version = utils.determine_next_version()
@@ -190,12 +174,10 @@
 
 
 def test_determine_next_version_on_release_branch_with_active_rc(mocker):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="release/0.37"
-    )
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="release/0.37")
     # 0.37.0-rc0 and rc1 exist, but no stable 0.37.0 yet
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["0.37.0-rc0", "0.37.0-rc1", "0.36.0"],
     )
 
@@ -208,12 +190,10 @@
 def test_determine_next_version_on_release_branch_with_stable_and_active_patch_rc(
     mocker,
 ):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="release/0.37"
-    )
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="release/0.37")
     # 0.37.0 stable exists, and 0.37.1-rc0 exists (but no stable 0.37.1 yet)
     mocker.patch(
-        "tools.private.release.git.Git.get_tags",
+        "dev.release.git.Git.get_tags",
         return_value=["0.37.0", "0.37.1-rc0", "0.36.0"],
     )
 
@@ -224,10 +204,8 @@
 
 
 def test_determine_next_version_on_main_branch_fallback(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file.bzl").write_text("no markers here")
 
     next_version = utils.determine_next_version()
@@ -272,9 +250,9 @@
     bazel_dir.mkdir()
     (bazel_dir / "mock_file.bzl").write_text(mock_file_content)
 
-    tools_dir = release_tool_env.git_root / "tools" / "private" / "release"
-    tools_dir.mkdir(parents=True)
-    (tools_dir / "mock_file.bzl").write_text(mock_file_content)
+    dev_dir = release_tool_env.git_root / "dev" / "release"
+    dev_dir.mkdir(parents=True)
+    (dev_dir / "mock_file.bzl").write_text(mock_file_content)
 
     tests_dir = release_tool_env.git_root / "tests" / "tools" / "private" / "release"
     tests_dir.mkdir(parents=True)
@@ -292,7 +270,7 @@
     new_content = (bazel_dir / "mock_file.bzl").read_text()
     assert "VERSION_NEXT_FEATURE" in new_content
 
-    new_content = (tools_dir / "mock_file.bzl").read_text()
+    new_content = (dev_dir / "mock_file.bzl").read_text()
     assert "VERSION_NEXT_FEATURE" in new_content
 
     new_content = (tests_dir / "mock_file.bzl").read_text()
@@ -300,10 +278,8 @@
 
 
 def test_determine_next_version_ignores_agents_markers(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     agents_dir = release_tool_env.git_root / ".agents"
     agents_dir.mkdir()
     (agents_dir / "mock_file.md").write_text(":::{versionadded} VERSION_NEXT_FEATURE")
@@ -332,10 +308,8 @@
 
 
 def test_determine_next_version_on_main_with_is_patch(mocker, release_tool_env):
-    mocker.patch(
-        "tools.private.release.git.Git.get_current_branch", return_value="main"
-    )
-    mocker.patch("tools.private.release.utils.get_latest_version", return_value="1.2.3")
+    mocker.patch("dev.release.git.Git.get_current_branch", return_value="main")
+    mocker.patch("dev.release.utils.get_latest_version", return_value="1.2.3")
     (release_tool_env.git_root / "mock_file.bzl").write_text(
         ":::{versionadded} VERSION_NEXT_FEATURE"
     )
diff --git a/tools/private/README.md b/tools/private/README.md
index ab2847d..bf1ad8d 100644
--- a/tools/private/README.md
+++ b/tools/private/README.md
@@ -1,9 +1,11 @@
-# Developer Tools (`//tools/private`)
+# Internal Tools (`//tools/private`)
 
-This directory contains development-only tools used for maintaining and
-developing `rules_python` itself (such as release management, dependency
-updating, and repository maintenance scripts).
+This directory contains internal private tools and dependencies used by
+`//tools` (such as `publish_deps.bzl`).
 
 Supporting tools for rules (e.g. `launcher`, `precompiler`, `zipapp`,
 `publish`, `wheelmaker`) belong as their own top-level directories under
 `//tools/`.
+
+Developer-only tools (such as release management and dependency updating)
+belong under `//dev/`.