Skip to content

refactor(cli): extract interactive menu into cli/interactive.py with CliContext#229

Merged
ArtVsMark merged 1 commit into
mainfrom
refactor/cli-interactive-extraction
Jul 9, 2026
Merged

refactor(cli): extract interactive menu into cli/interactive.py with CliContext#229
ArtVsMark merged 1 commit into
mainfrom
refactor/cli-interactive-extraction

Conversation

@ArtVsMark

Copy link
Copy Markdown
Owner

Summary

Issue #121 Phase 2, part of the cli.py decomposition epic (#117). Phase 1 (rendering extraction) already merged. Branched from fresh main (#118-#120/#121-Phase-1 all merged).

Extracts the interactive-menu/prompt subsystem — _interactive_menu, _ask_bench_profile/_ask_micro_profile/_ask_number, _print_menu, _pick_path_via_dialog/_prompt_path/_resolve_cli_path_or_error, _BENCH_PROFILES/_MICRO_PROFILES — out of cli/__init__.py into a new leaf module cli/interactive.py, following the CliContext-based DI pattern #120 already proved (842/842 tests passed there with zero test changes).

What I verified before designing

Grepped every test file to find exactly which of these names are directly monkeypatched via cli.<name> (expecting effect to propagate through the call chain) vs. never patched: _ask_bench_profile, _ask_micro_profile, _pick_path_via_dialog, and _interactive_menu are; _print_menu, _ask_number, _prompt_path, _resolve_cli_path_or_error, and the two profile dicts are not. CliContext gains 7 new fields for exactly the directly-patched names; _print_menu/_prompt_path stay as ordinary same-module bare calls inside interactive.py since nothing independently patches them — confirmed, not assumed — and they still correctly propagate patches on their own dependencies (e.g. _prompt_pathctx.pick_path_via_dialog).

The i18n-state scope decision

The issue's Phase 2 text groups these functions with _LANG/_MESSAGES/_LOCALE_MESSAGES/_t. I deliberately did not move the i18n state — main() does global _LANG; _LANG = args.lang, a real runtime mutation, not just monkeypatch plumbing. If _LANG physically moved to a new module, cli/__init__.py's re-export would become a one-time snapshot instead of a live alias, silently breaking monkeypatch.setattr(cli, "_LANG", "en") (an autouse fixture covering ~68 tests in test_cli.py) — the exact class of hazard #120 was designed to avoid, but worse here because it's real state, not just test plumbing. A Plan agent independently re-verified this before implementation and confirmed it as a hard blocker, not a cautious guess. _LANG/_MESSAGES/_LOCALE_MESSAGES/_t stay in cli/__init__.py, unmoved — treated as a settled architectural boundary, not a "later" TODO.

Result

Zero test files changed. The full suite (842 tests) passes unmodified.

This design was validated by a Plan agent before implementation (independently re-verified the monkeypatch inventory, the context-field-vs-bare-call rule, and the _LANG mutation hazard) and approved via plan mode before any code was written.

Test plan

  • pytest tests/ -q — 842 passed, 3 skipped, 0 test files modified (deselected the 2 pre-existing unrelated failures already failing on main)
  • ruff check src tests docs / ruff format --check src tests — clean
  • mypy src/stepik_grader --ignore-missing-imports — clean
  • Manual smoke: interactive menu (mode 0 exit via piped stdin), --mode 1 --file ... --output json, interactive-menu-driven mode 1 path prompt — all exercised the new wrapper → interactive.pyCliContext chain end-to-end
  • from stepik_grader.cli import main, import stepik_grader.grader — facade re-exports intact
  • Updated docs/architecture.md (module table + DAG) and docs/project-structure.md for the new cli/interactive.py leaf module and CliContext's extended field list

🤖 Generated with Claude Code

…CliContext

Splits the interactive-menu/prompt subsystem (_interactive_menu,
_ask_bench_profile/_ask_micro_profile/_ask_number, _print_menu,
_pick_path_via_dialog/_prompt_path/_resolve_cli_path_or_error,
_BENCH_PROFILES/_MICRO_PROFILES) out of cli/__init__.py into a new leaf
module cli/interactive.py, following the CliContext-based DI pattern
#120 already proved (842/842 tests passed there unmodified).

Grepped every test file to find exactly which of these names are
directly monkeypatched via cli.<name> (expecting effect through the
call chain) versus never patched: _ask_bench_profile, _ask_micro_profile,
_pick_path_via_dialog, and _interactive_menu are; _print_menu,
_ask_number, _prompt_path, _resolve_cli_path_or_error, and the two
profile dicts are not. CliContext (cli/context.py) gains 7 new fields
(pick_path_via_dialog, ask_bench_profile, ask_micro_profile,
run_mode_1..4) for exactly the directly-patched names; _print_menu and
_prompt_path stay as ordinary same-module bare calls inside
interactive.py since nothing independently patches them (confirmed, not
assumed) -- they still correctly observe patches on their own
dependencies since those flow through ctx.

_LANG/_MESSAGES/_LOCALE_MESSAGES/_t deliberately stay in cli/__init__.py,
unmoved -- main() does `global _LANG; _LANG = args.lang`, a real runtime
mutation. Moving _LANG would make cli/__init__.py's re-export a one-time
snapshot instead of a live alias, silently breaking
monkeypatch.setattr(cli, "_LANG", "en") (an autouse fixture covering
~68 tests in test_cli.py) without any test failing loudly at the call
site that actually broke. This scope boundary was independently
re-verified by a Plan agent before implementation and is treated as
settled, not just deferred.

cli/__init__.py keeps thin wrapper functions with the original public
signatures for every directly-patched name (matching #120's _run_mode_1..4
pattern) and plain re-exports (no wrapper) for _ask_number/
_BENCH_PROFILES/_MICRO_PROFILES, matching cli/options.py's precedent.
grader.py needs zero changes -- all 7 names it imports from
stepik_grader.cli remain resolvable there.

Zero test files changed -- the full suite (842 tests) passes unmodified.
Updated docs/architecture.md and docs/project-structure.md for the new
cli/interactive.py leaf module and CliContext's extended field list.

Issue #121 (Phase 2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ArtVsMark ArtVsMark merged commit 96f8eb4 into main Jul 9, 2026
9 checks passed
@ArtVsMark ArtVsMark deleted the refactor/cli-interactive-extraction branch July 9, 2026 08:35
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.

1 participant