0005 — Split large command modules by responsibility¶
- Status: Accepted
- Deciders: rhiza-tools maintainers
Context¶
Command modules naturally mix three concerns: the Typer command surface and
orchestration, version/bump logic, and git plumbing. Left together they grow
past the size at which a file is easy to reason about (the original bump.py
crossed this line in #207).
Decision¶
When a command module grows large, extract a cohesive responsibility into a sibling module and re-export the moved names from the original module so the public import surface — and existing tests — are unchanged. The established pattern:
| Command | Surface / orchestration | Extracted module(s) |
|---|---|---|
bump |
bump.py |
bump_versioning.py, bump_engine.py (the adapter, ADR-0001) |
release |
release.py |
release_versioning.py (bump-type resolution) |
rollback |
rollback.py |
rollback_git.py (git tag/commit plumbing) |
Because the helpers are re-exported, callers and tests continue to import
release.<helper> / rollback.<helper>. When a moved function looks up a
dependency in its new module's namespace, the corresponding test patch
target moves with it (e.g. release_versioning.bump_command,
rollback_git.run_git_command).
The test_module_size gate enforces the
ceiling that triggers this; after the #223 split the largest command module is
~700 lines and the gate is set to 750.
Amendment — sibling suffixes became subpackages¶
The flat suffix files described above were later folded into per-command
subpackages: bump_versioning.py → bump/versioning.py, release_git.py →
release/git.py, and so on, with the orchestration module becoming the
package's __init__.py. The convention is otherwise unchanged — the
__init__.py re-exports the extracted helpers, dependencies still point
downward only, and patch targets follow the dependency into the new module
namespace (now commands.release.versioning.bump_command,
commands.rollback.git.run_git_command). See
the Architecture guide for the current layout; tests/
mirrors it under tests/commands/<command>/.
Consequences¶
- Each module has a single clear responsibility and stays navigable.
- The re-export convention keeps refactors non-breaking for importers.
- Splitting interacts with the test suite's module-namespace patching: moving code requires moving the patch target to where the dependency now resolves.