Skip to content

refactor: create commands/ package and move init handler (PR-4/8)#2615

Open
darion-yaphet wants to merge 7 commits into
github:mainfrom
darion-yaphet:refactor/split-init-pr4
Open

refactor: create commands/ package and move init handler (PR-4/8)#2615
darion-yaphet wants to merge 7 commits into
github:mainfrom
darion-yaphet:refactor/split-init-pr4

Conversation

@darion-yaphet
Copy link
Copy Markdown
Contributor

Description

Part 4 of 8 in the init.py module split refactor.

Creates the commands/ subpackage and moves the init command handler (~670 lines) out of init.py into commands/init.py, using a register(app) pattern to avoid
circular imports.

New modules:

  • _agent_config.py — extracts AGENT_CONFIG, AI_ASSISTANT_HELP, AI_ASSISTANT_ALIASES, SCRIPT_TYPE_CHOICES, DEFAULT_INIT_INTEGRATION as a base-layer module (no internal
    imports), breaking the circular dependency that would otherwise occur when commands/init.py imports from init.py
  • commands/init.py — package marker
  • commands/init.py — init command via register(app) pattern; dependencies still in init.py are resolved via lazy imports inside the command body
  • commands/{integration,preset,extension,workflow}.py — placeholder stubs for future PRs

Backward compatibility: All re-exported symbols (AGENT_CONFIG, AI_ASSISTANT_HELP, SCRIPT_TYPE_CHOICES, select_with_arrows, _locate_bundled_workflow) remain importable
from specify_cli.

init.py reduction: ~780 lines removed.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

uv run pytest: 2910 passed, 35 skipped. Fixed one test whose mock.patch target moved from specify_cli._stdin_is_interactive →
specify_cli.commands.init._stdin_is_interactive.

AI Disclosure

  • I did use AI assistance (describe below)

Code structure and implementation generated with Claude Code; reviewed and validated manually.

@darion-yaphet darion-yaphet requested a review from mnriem as a code owner May 18, 2026 05:47
@darion-yaphet darion-yaphet changed the title Refactor/split init pr4 refactor: create commands/ package and move init handler (PR-4/8) May 18, 2026
@mnriem mnriem requested a review from Copilot May 20, 2026 12:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the specify_cli package by introducing a commands/ subpackage and moving the specify init command implementation out of src/specify_cli/__init__.py, while preserving backwards-compatible imports and CLI behavior.

Changes:

  • Add src/specify_cli/commands/ package and move the init command into commands/init.py using a register(app) pattern.
  • Extract agent-related constants into a new base module src/specify_cli/_agent_config.py and re-export them from specify_cli.
  • Add/adjust tests to ensure the new package layout is importable and that init remains registered; update one mock patch target.
Show a summary per file
File Description
tests/test_commands_package.py Adds import/registration coverage for the new commands/ package and agent-config re-exports.
tests/integrations/test_integration_claude.py Updates mock.patch targets to the new commands.init locations.
src/specify_cli/commands/init.py Introduces the new commands package marker.
src/specify_cli/commands/init.py New home for the specify init command with register(app) wiring.
src/specify_cli/commands/extension.py Placeholder stub for future extraction.
src/specify_cli/commands/integration.py Placeholder stub for future extraction.
src/specify_cli/commands/preset.py Placeholder stub for future extraction.
src/specify_cli/commands/workflow.py Placeholder stub for future extraction.
src/specify_cli/_agent_config.py Extracts agent/integration constants from __init__.py.
src/specify_cli/init.py Re-exports agent config constants and registers init via commands.init.register(app).

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 10/10 changed files
  • Comments generated: 4

Comment thread src/specify_cli/commands/init.py Outdated
Comment thread src/specify_cli/commands/init.py Outdated
Comment thread src/specify_cli/commands/init.py Outdated
Comment thread src/specify_cli/commands/__init__.py Outdated
@darion-yaphet darion-yaphet force-pushed the refactor/split-init-pr4 branch 2 times, most recently from 6ffc129 to ed34f67 Compare May 21, 2026 05:09
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 21, 2026

Note for each of the subcommands I would probably like to have the commands co-located with their actual subdirectory where the rest of the functionality lives. E.g integrations in integrations directory.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 10/10 changed files
  • Comments generated: 1

Comment thread src/specify_cli/commands/init.py
- Extract agent configuration constants (AGENT_CONFIG, AI_ASSISTANT_HELP,
  SCRIPT_TYPE_CHOICES, etc.) to _agent_config.py to avoid circular imports
- Create commands/ package skeleton with stub modules for each command group
- Move init command handler (~670 lines) from __init__.py to commands/init.py
  using the register(app) pattern; lazy imports inside the handler body
  prevent circular dependencies with __init__.py
- Re-export AGENT_CONFIG, AI_ASSISTANT_HELP, SCRIPT_TYPE_CHOICES from
  __init__.py for backward compatibility
- Add tests/test_commands_package.py to verify package structure
…s/init.py

_stdin_is_interactive and select_with_arrows are now bound in
specify_cli.commands.init, not specify_cli directly.
- Remove shutil, shlex top-level imports (used lazily inside functions)
- Remove rich.live.Live import (moved to commands/init.py)
- Mark select_with_arrows and _locate_bundled_workflow as explicit
  re-exports to satisfy ruff F401
Aligns with the project convention established in _console.py, _assets.py,
_utils.py, and other modules.
Potential fix for pull request finding

Update command package documentation and init help text to reflect the current implementation: init uses bundled assets and integration setup, while placeholder command modules are import anchors until extracted. Remove the unused tracker-active flag assignment that had no reader in the codebase.

Constraint: --offline is hidden/no-op and init no longer downloads templates from GitHub releases

Rejected: Add no-op register functions to placeholder modules | would imply extracted command groups are implemented there

Confidence: high

Scope-risk: narrow

Directive: Keep CLI help text aligned with the actual init scaffolding path

Tested: uv run specify init --help; uv run pytest tests/test_commands_package.py tests/test_agent_config_consistency.py -q; uv run pytest tests/test_commands_package.py tests/test_console_imports.py tests/integrations/test_cli.py -q

Not-tested: full test suite
Use the _print_cli_warning helper (introduced in main) for preset install
failures so that output matches the expected format:
  "Failed to install preset '<name>': ..."
  "Continuing without the optional preset."
@darion-yaphet darion-yaphet force-pushed the refactor/split-init-pr4 branch 2 times, most recently from ce29905 to d5a0fc6 Compare May 21, 2026 17:02
@darion-yaphet
Copy link
Copy Markdown
Contributor Author

I will fix the issue of moving to the subdirectory later.

The init command imported CLI error formatting helpers through its circular-dependency-safe lazy import block, but the module does not use them. Remove those imports so ruff does not report F401.

Constraint: uvx ruff check src/ must pass.

Rejected: Wire the helpers into init error handling | Existing preset warnings already use _print_cli_warning, and changing behavior is unnecessary for this lint fix.

Confidence: high

Scope-risk: narrow

Directive: Keep lazy import blocks limited to names consumed in the importing module.

Tested: uvx ruff check src/

Not-tested: Full pytest suite

Co-authored-by: OmX <omx@oh-my-codex.dev>
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 21, 2026

So not sure if we should merge this as you are going to have to move each of the sub commands into their corresponding directory anyway? Thoughts?

@darion-yaphet
Copy link
Copy Markdown
Contributor Author

So not sure if we should merge this as you are going to have to move each of the sub commands into their corresponding directory anyway? Thoughts?

I think I didn't explain it clearly enough.
In this PR, I will move the commands to a subdirectory. But I really don't have time today. I will make the changes later.

@darion-yaphet
Copy link
Copy Markdown
Contributor Author

PR-4 lays the architectural foundation. It introduces the commands/ package and moves the init command (~670 lines) from init.py into commands/init.py. As part of
this, agent configuration constants (AGENT_CONFIG, AI_ASSISTANT_HELP, etc.) are extracted into a dedicated _agent_config.py module to eliminate the circular import
that would otherwise arise in later PRs. This PR also establishes the migration pattern that all subsequent PRs follow: each command module exposes a register(app)
function called by init.py at startup, and any shared helpers still living in init.py are accessed via lazy imports inside command function bodies to keep the
dependency graph acyclic.

PRs 5 through 8 are straightforward applications of that same pattern, relocating the remaining four command groups one PR at a time:

  • PR-5 moves the 11 integration subcommands (list, install, use, uninstall, switch, upgrade, search, info, and the three catalog subcommands) into
    integrations/_commands.py (~1,200 lines)
  • PR-6 moves the 12 preset subcommands into presets/_commands.py (~730 lines)
  • PR-7 moves the 10 extension subcommands into extensions/_commands.py (~1,250 lines)
  • PR-8 moves the 9 workflow subcommands into workflows/_commands.py (~625 lines)

Once all four PRs land, init.py is reduced from ~4,500 lines to a ~500-line registration layer — little more than imports and register() calls.

A few things I'd love your input on:

  • Co-location convention — we're placing _commands.py alongside the domain code rather than keeping everything under a central commands/ directory. Does this feel
    intuitive when navigating the codebase, or would you prefer a different layout?
  • The register(app) pattern — each command module wires itself into the Typer app via a single register() call. Any concerns about discoverability or testability with
    this approach?
  • PR granularity — we're splitting this into one PR per command group so each diff stays reviewable. Would you rather see fewer, larger PRs, or is this cadence
    working for you?
  • Anything I might have missed — if you spot edge cases, naming issues, or patterns that conflict with other parts of the codebase, please flag them early.

Happy to walk through any part of this in more detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants