Is your feature request related to a problem? Please describe.
Two source files have grown far beyond maintainable size:
src/apm_cli/cli.py — ~4,675 lines containing 15+ Click commands, 60+ helper functions, and an 860-line install engine all in a single file.
src/apm_cli/models/apm_package.py — ~1,186 lines mixing dataclasses, enums, a 377-line DependencyReference.parse() method, and validation logic.
This makes parallel development difficult, increases merge conflicts, and hurts code navigability. The rest of the codebase follows good modular patterns (50–200 line files).
Describe the solution you'd like
Phase 1 — Extract CLI commands from cli.py into commands/
Reduce cli.py from ~4,675 → ~120 lines (thin wiring layer). Follow the existing commands/deps.py pattern.
| New file |
What moves there |
~Lines |
commands/_helpers.py |
Shared utilities: _get_console(), lazy loaders, color constants, _check_orphaned_packages(), _atomic_write() |
~250 |
commands/init.py |
init() command + _interactive_project_setup(), _auto_detect_author(), _get_default_config(), _create_minimal_apm_yml() |
~400 |
commands/install.py |
install() command + _install_apm_dependencies() (860-line core engine), _validate_and_add_packages_to_apm_yml(), MCP dep helpers, summary display |
~1,400 |
commands/uninstall.py |
uninstall() command + transitive cleanup |
~420 |
commands/prune.py |
prune() command |
~150 |
commands/update.py |
update() command |
~130 |
commands/compile.py |
compile() command + _watch_mode(), validation display helpers |
~700 |
commands/run.py |
run() + preview() commands + runtime detection helpers |
~350 |
commands/list.py |
list() command |
~90 |
commands/config.py |
config group (set/get) |
~160 |
commands/runtime.py |
runtime group (setup/list/remove/status) |
~80 |
commands/mcp.py |
mcp group (search/show/list) |
~200 |
cli.py becomes a thin wiring layer: @click.group, cli.add_command(...), print_version(), and main().
Design rules:
- The
set = builtins.set / list = builtins.list trick must move to any command file whose Click name clashes with builtins.
_helpers.py must NOT import from command modules (prevents circular imports).
- Command modules must NOT import from each other — shared logic goes in
_helpers.py.
Phase 2 — Split apm_package.py into focused model files
Reduce apm_package.py from ~1,186 → ~400 lines.
| New file |
What moves there |
~Lines |
models/dependency.py |
DependencyReference (incl. 377-line parse()), ResolvedReference, GitReferenceType, parse_git_reference() |
~650 |
models/validation.py |
ValidationResult, ValidationError, PackageType, validate_apm_package() + private validators, _has_hook_json(), InvalidVirtualPackageExtensionError |
~300 |
apm_package.py (slimmed) |
APMPackage, PackageInfo, PackageContentType, cache logic |
~400 |
models/__init__.py re-exports all symbols so from apm_cli.models import X keeps working everywhere.
Verification
uv run pytest -q — full test suite, zero regressions
uv run python -c "from apm_cli.cli import cli; print('OK')" — no circular imports
uv run python -c "from apm_cli.models import DependencyReference, APMPackage, ValidationResult, ResolvedReference, PackageInfo; print('OK')" — re-exports intact
uv run pytest tests/benchmarks/ -q — no perf regression
uv run apm --version && uv run apm --help — CLI smoke test
Describe alternatives you've considered
- Partial extraction (only the largest commands like
install and compile): Would help but still leaves a 2,000+ line cli.py. A full extraction is cleaner and sets the right pattern going forward.
- Keeping models in one file: The 377-line
parse() method and validation logic are sufficiently independent domains to justify separate files. Leaving them mixed increases cognitive load.
Additional context
- Phases 1 and 2 are independent and can be done in parallel or either order.
_install_apm_dependencies() (860 lines) is a candidate for further decomposition into resolve/download/integrate phases, but that should be a separate follow-up to limit blast radius.
- No user-facing behavior changes — this is pure internal restructuring.
- The existing
commands/deps.py (823 lines with list, tree, clean, update, info subcommands) serves as the template for new command files.
Is your feature request related to a problem? Please describe.
Two source files have grown far beyond maintainable size:
src/apm_cli/cli.py— ~4,675 lines containing 15+ Click commands, 60+ helper functions, and an 860-line install engine all in a single file.src/apm_cli/models/apm_package.py— ~1,186 lines mixing dataclasses, enums, a 377-lineDependencyReference.parse()method, and validation logic.This makes parallel development difficult, increases merge conflicts, and hurts code navigability. The rest of the codebase follows good modular patterns (50–200 line files).
Describe the solution you'd like
Phase 1 — Extract CLI commands from
cli.pyintocommands/Reduce
cli.pyfrom ~4,675 → ~120 lines (thin wiring layer). Follow the existingcommands/deps.pypattern.commands/_helpers.py_get_console(), lazy loaders, color constants,_check_orphaned_packages(),_atomic_write()commands/init.pyinit()command +_interactive_project_setup(),_auto_detect_author(),_get_default_config(),_create_minimal_apm_yml()commands/install.pyinstall()command +_install_apm_dependencies()(860-line core engine),_validate_and_add_packages_to_apm_yml(), MCP dep helpers, summary displaycommands/uninstall.pyuninstall()command + transitive cleanupcommands/prune.pyprune()commandcommands/update.pyupdate()commandcommands/compile.pycompile()command +_watch_mode(), validation display helperscommands/run.pyrun()+preview()commands + runtime detection helperscommands/list.pylist()commandcommands/config.pyconfiggroup (set/get)commands/runtime.pyruntimegroup (setup/list/remove/status)commands/mcp.pymcpgroup (search/show/list)cli.pybecomes a thin wiring layer:@click.group,cli.add_command(...),print_version(), andmain().Design rules:
set = builtins.set/list = builtins.listtrick must move to any command file whose Click name clashes with builtins._helpers.pymust NOT import from command modules (prevents circular imports)._helpers.py.Phase 2 — Split
apm_package.pyinto focused model filesReduce
apm_package.pyfrom ~1,186 → ~400 lines.models/dependency.pyDependencyReference(incl. 377-lineparse()),ResolvedReference,GitReferenceType,parse_git_reference()models/validation.pyValidationResult,ValidationError,PackageType,validate_apm_package()+ private validators,_has_hook_json(),InvalidVirtualPackageExtensionErrorapm_package.py(slimmed)APMPackage,PackageInfo,PackageContentType, cache logicmodels/__init__.pyre-exports all symbols sofrom apm_cli.models import Xkeeps working everywhere.Verification
uv run pytest -q— full test suite, zero regressionsuv run python -c "from apm_cli.cli import cli; print('OK')"— no circular importsuv run python -c "from apm_cli.models import DependencyReference, APMPackage, ValidationResult, ResolvedReference, PackageInfo; print('OK')"— re-exports intactuv run pytest tests/benchmarks/ -q— no perf regressionuv run apm --version && uv run apm --help— CLI smoke testDescribe alternatives you've considered
installandcompile): Would help but still leaves a 2,000+ line cli.py. A full extraction is cleaner and sets the right pattern going forward.parse()method and validation logic are sufficiently independent domains to justify separate files. Leaving them mixed increases cognitive load.Additional context
_install_apm_dependencies()(860 lines) is a candidate for further decomposition into resolve/download/integrate phases, but that should be a separate follow-up to limit blast radius.commands/deps.py(823 lines withlist,tree,clean,update,infosubcommands) serves as the template for new command files.