refactor: tighten stage 1 ruff thresholds#1113
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors several high-complexity/return-pressure hotspots to meet Stage 1 Ruff thresholds, and updates the Ruff thresholds in pyproject.toml accordingly (per #1077).
Changes:
- Updates Ruff complexity/args/returns thresholds to Stage 1 targets in
pyproject.toml. - Extracts helper functions to reduce complexity/returns in MCP installation and marketplace publishing flows.
- Reduces install wrapper argument pressure by introducing an options dataclass and
**kwargsvalidation.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/apm_cli/policy/policy_checks.py | Refactors check execution into iterable loops for dependency + MCP policy checks. |
| src/apm_cli/marketplace/semver.py | Simplifies comparison operator handling via a small operator/comparator table. |
| src/apm_cli/marketplace/publisher.py | Splits per-target publish processing into smaller checkout/load/guard/write/commit/push helpers. |
| src/apm_cli/integration/mcp_integrator.py | Extracts runtime selection, dependency splitting, registry/self-defined install, and summary helpers to reduce install complexity. |
| src/apm_cli/install/validation.py | Adds targeted Ruff waivers for legacy complexity outliers. |
| src/apm_cli/install/services.py | Adds targeted Ruff waiver for max-args on a legacy entry point. |
| src/apm_cli/install/mcp/conflicts.py | Adds targeted Ruff waiver for max-args on a legacy validator. |
| src/apm_cli/install/mcp/command.py | Adds targeted Ruff waiver for max-args on a legacy command wrapper. |
| src/apm_cli/commands/install.py | Introduces frozen options dataclass for _install_apm_dependencies and switches wrapper to **kwargs. |
| pyproject.toml | Tightens Ruff thresholds to Stage 1 values (statements/branches/complexity/args/returns). |
|
Hey @zeel2104 -- welcome and thanks for picking up #1077! The strangler-fig direction here is exactly right, and the threshold reductions are meaningful. Great first contribution. I can see that commit There are still a few CI blockers that need addressing before we can proceed: CI failures
RebaseThe branch currently has merge conflicts with Once these are resolved, we will trigger a full panel evaluation of the PR. Happy to help if you have any questions -- just ping here. Thanks again! |
|
Thanks for the follow-up. I addressed the remaining Ruff/CI and merge-conflict issues. |
sergio-sisternes-epam
left a comment
There was a problem hiding this comment.
Hey @zeel2104 -- great progress on the strangler Stage 1 work! The Ruff threshold tightening and the mcp_integrator / publisher decompositions are solid. A few items remain before this fully satisfies #1077 -- details in the inline comments below.
Additionally, #1077 requires three files brought under 1400 lines: github_downloader.py (2316), commands/install.py (1680), and skill_integrator.py (1365). None of these have been trimmed in this PR, and commands/install.py appears to have grown. All three need to be addressed here -- this PR should fully close #1077 as stated in its "Fixes" line.
🔍 APM Review Panel — PR #1113
Panel Composition
What This PR Does
🏗️ Architecture — APPROVEThe decomposition follows SRP faithfully. Each extracted helper has a single, describable responsibility and a clear return type. The strangler-fig staging is explicit ( The static-method pattern is appropriate here — these helpers carry no instance state and benefit from explicit dependency injection through their parameters. Minor concern: 🔒 Correctness — APPROVE WITH NOTEThe decomposition appears behaviorally equivalent to the original monolith. Key logical flows are preserved:
One edge-case flag: In 🧹 Code Quality — APPROVE WITH NOTEPositive:
Note on 📐 API Design — APPROVE
CEO ArbitrationNo panel conflicts requiring arbitration. All reviewers converge: the refactoring is sound, the behavioral equivalence is high-confidence, and the threshold reductions are genuine improvements. The notes above are refinements for follow-up PRs, not blockers. 📋 Advisory Recommendation✅ APPROVE — with the following tracked follow-ups (non-blocking):
This PR is a clean, well-scoped refactoring. The threshold numbers are honest — they reflect actual code improvements, not just adjusted lint ceilings. Recommend merge. Note 🔒 Integrity filter blocked 2 itemsThe following items were blocked because they don't meet the GitHub integrity level.
To allow these resources, lower tools:
github:
min-integrity: approved # merged | approved | unapproved | none
|
|
@sergio-sisternes-epam
Local verification:
|
f2427d0 to
0c178e0
Compare
|
@zeel2104, main is moving fast in parallel to our refactor. Please fix the conflicts in |
Head branch was pushed to by a user without write access
2ae81dc to
b6fa2de
Compare
|
Thanks for the heads-up. I rebased the PR branch onto latest main and resolved the new conflicts in For |
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 2 | Solid strangler-fig Stage 1; free-function-as-method pattern in github_downloader_packages.py needs typed self; Protocol additions are a clear win. |
| CLI Logging Expert | 0 | 1 | 1 | mcp_stale_cleanup.py bypasses the logger for 4 of 6 runtimes, emitting _rich_success unconditionally even when a CommandLogger is present; all other new modules are clean. |
| DevX UX Expert | 1 | 0 | 3 | One blocking regression: --frozen flag removed from apm install CLI despite being documented as the recommended CI workflow; two nits on help text truncation. |
| Supply Chain Security Expert | 0 | 0 | 1 | Refactor preserves auth/path-safety invariants; one pre-existing token-in-debug-log risk carried into the new module warrants a nit. |
| OSS Growth Hacker | 0 | 2 | 1 | Refactor is a net positive for contributor onboarding; tightened thresholds are credibility signals. Add a CHANGELOG entry and a CONTRIBUTING note to amplify the growth surface. |
| Auth Expert | 1 | 0 | 1 | Token leak in _try_sparse_checkout debug log when 'git remote add' fails; auth-resolver chain in validation.py is preserved correctly. |
| Doc Writer | 0 | 1 | 1 | Pure internal refactoring; no user-facing behavior change. Two minor doc gaps: missing CHANGELOG entry and dev guide omits CI complexity-threshold enforcement pattern. |
| Test Coverage Expert | 0 | 3 | 0 | 9 new modules, 0 test files added; mcp_stale_cleanup and github_downloader_packages carry the highest silent-drift risk on critical-promise surfaces. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 5 follow-ups
- [Auth Expert] (blocking-severity) Mask auth_url before debug logging in _try_sparse_checkout when 'git remote add' fails -- Raw PAT/bearer token written to stderr for any user running APM_DEBUG=1 against a private repo. One-line fix; pattern already exists in adjacent code paths.
- [DevX UX Expert] (blocking-severity) Restore --frozen Click option and mutex guard to apm install -- Flag is documented as the recommended CI workflow; deletion silently disables lockfile enforcement for all callers. Internal frozen field survives but is never populated from the CLI.
- [CLI Logging Expert] Replace bare _rich_success() calls in mcp_stale_cleanup.py with logger.success() for all 6 runtimes -- Quiet-mode, test capture, and dry-run suppression currently work for vscode/opencode and silently break for copilot/codex/cursor/windsurf.
- [Test Coverage Expert] Add integration-with-fixtures test for mcp_stale_cleanup.py exercising real IDE config file mutation -- Missing test on a multi-harness surface; transitive coverage through mocked facade does not catch file-mutation regressions.
- [Test Coverage Expert] Add direct fixture-based tests for github_downloader_packages.py download and integrity paths -- 750-line module on a secure-by-default surface; existing tests mock at the boundary and miss behavioral changes inside the new module.
Architecture
classDiagram
direction LR
class GitHubPackageDownloader {
<<Strategy>>
+download_package()
+download_subdirectory_package()
+_try_sparse_checkout()
+_get_clone_progress_callback()
}
class github_downloader_packages {
<<Module>>
+download_package(self)
+download_subdirectory_package(self)
+_try_sparse_checkout(self)
+_get_clone_progress_callback(self)
}
class GitProgressReporter {
<<ValueObject>>
+update(op_code, cur_count, max_count)
}
class MCPIntegrator {
<<Facade>>
+configure()
+remove_stale()
}
class _RuntimeManager {
<<Protocol>>
+is_runtime_available(runtime_name)
}
class _MCPInstallLogger {
<<Protocol>>
+progress(message)
+warning(message)
+error(message)
+success(message)
}
class mcp_stale_cleanup {
<<Module>>
+remove_stale(stale_names, runtime, scope)
}
class SkillIntegrator {
<<BaseIntegrator>>
+integrate_skill()
}
class skill_helpers {
<<Module>>
+SkillIntegrationResult
+integrate_skill_files()
}
GitHubPackageDownloader *-- github_downloader_packages : method injection
github_downloader_packages ..> GitProgressReporter : uses
MCPIntegrator ..> mcp_stale_cleanup : delegates remove_stale
MCPIntegrator ..> _RuntimeManager : structural typing
MCPIntegrator ..> _MCPInstallLogger : structural typing
SkillIntegrator ..> skill_helpers : delegates
class github_downloader_packages:::touched
class GitProgressReporter:::touched
class mcp_stale_cleanup:::touched
class skill_helpers:::touched
class _RuntimeManager:::touched
class _MCPInstallLogger:::touched
classDef touched fill:#fff3b0,stroke:#d47600
flowchart TD
A([apm install]) --> B[commands/install.py]
B --> C[install/mcp/handler.py]
C --> D[integration/mcp_integrator.py MCPIntegrator.configure]
D --> E[FS: write runtime config files]
B --> F[install/manifest_update.py]
F --> G[FS: write apm.lock.yaml]
B --> H[install/rollback.py]
H --> I[FS: restore prior state on failure]
J([apm install stale cleanup]) --> K[mcp_integrator.py MCPIntegrator.remove_stale]
K --> L[mcp_stale_cleanup.py remove_stale]
L --> M{runtime loop}
M --> N[vscode mcp.json]
M --> O[copilot mcp-config.json]
M --> P[codex config.toml]
M --> Q[cursor mcp.json]
M --> R[opencode.json]
M --> S[windsurf mcp_config.json]
T([apm install github dep]) --> U[deps/github_downloader.py]
U --> V[deps/github_downloader_packages.py method injected]
V --> W{sparse checkout?}
W -- yes --> X[git sparse-checkout]
W -- no --> Y[git clone via GitPython]
V --> Z[deps/github_progress.py GitProgressReporter]
Recommendation
Fix the two blocking items -- token redaction in _try_sparse_checkout and the --frozen CLI regression -- before merge. Both are small, targeted changes. Once those land, the seven recommended follow-ups are best tracked as a Stage 1.5 PR: logger consistency in mcp_stale_cleanup, integration tests for mcp_stale_cleanup and github_downloader_packages, typed self for the free-function-as-method pattern, and CHANGELOG/CONTRIBUTING entries. The structural direction is sound and worth shipping quickly after the two blocking fixes are in.
Full per-persona findings
Python Architect
-
[recommended] Free functions in github_downloader_packages.py take untyped
self-- IDE and type-checker blind spot atsrc/apm_cli/deps/github_downloader_packages.py:25
Functions _try_sparse_checkout, download_subdirectory_package, download_package, _get_clone_progress_callback are module-level callables that acceptselfwithout a type annotation, then are assigned as class attributes on GitHubPackageDownloader. mypy/pyright cannot reason aboutself.*accesses, so the entire method body is type-opaque. This cross-file method injection is the only instance of the pattern in the codebase and creates a maintenance surprise.
Suggested: Add a TYPE_CHECKING-guarded Protocol or forward-ref:if TYPE_CHECKING: from .github_downloader import GitHubPackageDownloaderand annotatedef _try_sparse_checkout(self: GitHubPackageDownloader, ...) -> bool: -
[nit] RUF013 suppressed with noqa instead of fixed in mcp_stale_cleanup.py at
src/apm_cli/integration/mcp_stale_cleanup.py:17
runtime: str = None # noqa: RUF013andexclude: str = None # noqa: RUF013suppress the implicit-Optional lint rather than changing the annotation tostr | None = None.
Suggested:runtime: str | None = None, exclude: str | None = None, -
[nit] remove_stale still carries C901/PLR0912 noqa after extraction -- complexity not reduced, only relocated at
src/apm_cli/integration/mcp_stale_cleanup.py:15
Moving from mcp_integrator.py preserved the per-runtime if-block structure. Stage 1 goal is file-size reduction so this is expected, but a follow-up to extract a_clean_runtime_config(runtime, config_path, key, stale, logger)helper would eliminate the noqa.
CLI Logging Expert
-
[recommended] mcp_stale_cleanup.py calls _rich_success() directly for copilot/codex/cursor/windsurf runtimes, bypassing the injected logger at
src/apm_cli/integration/mcp_stale_cleanup.py
remove_stale() replaces a None logger with NullCommandLogger so logger is guaranteed non-None. Yet copilot, codex, cursor, and windsurf stale-removal blocks call _rich_success() unconditionally instead of logger.success(). vscode and opencode correctly call logger.progress(). The inconsistency means quiet-mode, test capture, and dry-run suppression work for two runtimes and silently break for four others.
Suggested: Replace every bare_rich_success(...)call in remove_stale() withlogger.success(...). Remove the_rich_successimport once that is done. -
[nit] mcp_stale_cleanup.py uses logger.progress() for removal confirmations; logger.success() is the correct semantic at
src/apm_cli/integration/mcp_stale_cleanup.py
progress() maps to blue [>] (in-flight activity). Removal of a stale server is a completed action and should be logger.success() so it renders green [+] and matches the traffic-light rule.
DevX UX Expert
-
[blocking] --frozen flag silently removed from apm install, breaking documented CI workflow at
src/apm_cli/commands/install.py
The --frozen flag is documented as the recommended CI pattern ('apm install --frozen'). The flag and its mutex guard (--frozen + --update raise UsageError) were deleted from the install command. The internal frozen field still exists in InstallApmDependenciesOptions (frozen=options.frozen called at line 1392) but no CLI flag populates it -- it always defaults to False. Any CI pipeline following the published docs will silently stop enforcing lockfile freshness.
Suggested: Re-add@click.option("--frozen", is_flag=True, ...)to the install command, restore the frozen parameter in the function signature, and restore the mutex guardif frozen and update: raise click.UsageError(...).
Proof (missing):tests/integration/test_install_frozen.py-- proves: apm install --frozen rejects installs when lockfile is missing or stale, matching documented CI contract [devx,governed-by-policy] -
[nit] --update help text lost deprecation guidance pointing users to 'apm update' at
src/apm_cli/commands/install.py
Previous text included '(deprecated: prefer apm update for an interactive plan)'. New text drops this, reducing discoverability.
Suggested: Restore:help="Update dependencies to latest Git references (deprecated: prefer 'apm update' for an interactive plan, or 'apm update --yes' for CI)" -
[nit] --force help text truncated, dropping 'does NOT refresh refs; use apm update for that' guidance at
src/apm_cli/commands/install.py
The old help text explicitly told users that --force does not refresh refs. Dropping this increases the chance a user mistakenly uses --force when they need --update.
Suggested: Restore:help="Overwrite locally-authored files on collision and deploy despite critical security findings (does NOT refresh refs; use 'apm update' for that)" -
[nit] compile() still carries # noqa: C901, PLR0912, PLR0915 suppressions post-refactor at
src/apm_cli/commands/compile/cli.py:46
The function still exceeds Stage 1 strangler targets and will need its own dedicated split PR.
Suggested: Open a follow-up issue to split compile() the same way install.py was split.
Supply Chain Security Expert
- [nit] Raw auth_url (with embedded token) can appear in debug log when 'git remote add origin' fails in _try_sparse_checkout at
src/apm_cli/deps/github_downloader_packages.py
The cmd list contains auth_url verbatim. If that step fails,' '.join(cmd)is passed to_compat._debug, leaking the token into debug-mode log. Other error paths in the same file consistently call_redact_token()-- this one does not.
Suggested: Replace' '.join(cmd)with' '.join(_redact_token(c) for c in cmd)or mask the auth_url before inserting into cmds.
OSS Growth Hacker
-
[recommended] No CHANGELOG entry for Stage 1 strangler refactor leaves the OSS community unaware of the structural improvement at
CHANGELOG.md
Contributors scanning CHANGELOG to understand codebase trajectory miss a clear signal that APM is actively reducing complexity debt.
Suggested: Add under [Unreleased] > Changed: 'Stage 1 strangler refactor (Strangler Stage 1: tame the worst outliers #1077): split github_downloader.py, commands/install.py, skill_integrator.py, and marketplace/init.py into focused modules; complexity thresholds tightened (max-complexity 100->50, max-branches 115->60, MAX_LINES 2450->1400).' -
[recommended] No contributor-facing documentation of the strangler pattern or complexity-gate rationale at
CONTRIBUTING.md
First-time contributors hitting CI failure on max-complexity 50 with no guidance will open frustrated issues or abandon PRs.
Suggested: Add a 'Module complexity policy' section explaining the strangler fig pattern, pointing to issue Strangler Stage 1: tame the worst outliers #1077, listing current thresholds, and giving a one-command example. -
[nit] CI comment 'Stage 1 strangler target (Strangler Stage 1: tame the worst outliers #1077)' is internal jargon in CI output at
.github/workflows/ci.yml
External contributors reading CI output won't know what it means. Replace with 'complexity ceiling (see CONTRIBUTING.md#module-complexity-policy)'.
Auth Expert
-
[blocking] Token embedded in auth_url leaks to stderr via _debug when 'git remote add' fails at
src/apm_cli/deps/github_downloader_packages.py:82
In _try_sparse_checkout, auth_url contains the PAT/bearer token inline ((token/redacted)@host/...). The failure debug path does' '.join(cmd)on the full cmd list, which includes auth_url at index 3. Any user with APM_DEBUG set will have their token printed to stderr. Tokens in URLs must be masked before logging.
Suggested: Build a parallel 'safe_cmds' list where the 'git remote add origin url' entry has the token redacted. Pattern already used elsewhere: replace '(token/redacted)@' with 'https://***@'. -
[nit] dep_auth_scheme defaults to 'basic' when dep_auth_ctx is None, silently hiding a missing AuthContext at
src/apm_cli/deps/github_downloader_packages.py:43
If _resolve_dep_auth_ctx returns None, dep_auth_scheme silently falls back to 'basic' and _build_repo_url is called with a None token. Same behavior as before the split but harder to trace in the new module.
Doc Writer
-
[recommended] development-guide.md does not document the CI module-size and complexity threshold enforcement pattern at
docs/src/content/docs/contributing/development-guide.md
Contributors hitting CI failures on max-complexity 50 with no documented explanation. Development guide covers Ruff but not the enforced thresholds or ratchet strategy.
Suggested: Add a 'Complexity and module-size limits' subsection: explain the strangler fig pattern, note the thresholds live in pyproject.toml and ci.yml, state that thresholds are ratcheted down in dedicated refactoring PRs. -
[nit] CHANGELOG.md has no entry for the Stage 1 strangler refactoring at
CHANGELOG.md
The [Unreleased] section omits this structural refactoring. Keep a Changelog recommends recording changes that affect contributors.
Suggested: Under [Unreleased] > Changed: 'Stage 1 strangler-fig refactoring splits mcp_integrator.py, commands/install.py, and related monoliths into focused modules; CI complexity ceiling lowered from 100 to 50 (issue Strangler Stage 1: tame the worst outliers #1077).'
Test Coverage Expert
-
[recommended] mcp_stale_cleanup.py (329 lines) has no direct test at integration-with-fixtures tier at
src/apm_cli/integration/mcp_stale_cleanup.py
Existing tests call MCPIntegrator.remove_stale() with mocked config paths -- they verify the facade delegates but do not exercise the file-mutation logic against real IDE config fixtures. Grep of tests/ for mcp_stale_cleanup produced no integration-tier hit.
Suggested: Add a test writing a real Claude/Copilot config file with stale entries, call MCPIntegrator.remove_stale(), assert entries absent from disk.
Proof (missing at integration-with-fixtures):tests/integration/test_mcp_stale_cleanup_integration.py::test_remove_stale_entries_from_claude_config-- proves: apm install removes stale MCP server entries from target IDE configs when a package is replaced or removed [multi-harness-support,devx]
assert 'old-pkg' not in json.loads(config_path.read_text())['mcpServers'] -
[recommended] github_downloader_packages.py (750 lines) is covered only transitively; unit tests mock git.Repo before the new code runs at
src/apm_cli/deps/github_downloader_packages.py
tests/test_github_downloader.py mocks at the boundary so a subtle behavioral change inside github_downloader_packages would not be caught. Grep of tests/ for github_downloader_packages returned zero hits.
Suggested: Add a fixture-based test that stubs a local git repo and asserts download + integrity behavior without network mocking.
Proof (missing at integration-with-fixtures):tests/integration/test_github_downloader_packages_integration.py::test_download_package_from_local_git_fixture-- proves: apm install downloads and extracts a GitHub package without corrupting or silently skipping files [portability-by-manifest,secure-by-default]
assert (dest / 'apm.yml').exists() -
[recommended] Zero test files added for a 3000-line, 9-new-module refactor; no regression-trap guards the new module boundaries at
src/apm_cli/install/manifest_update.py
No test file appears in the diff (confirmed via diff inspection). While import chains are covered transitively, none of the 9 new modules has a test that explicitly imports and exercises the module's public API. A subtle signature change or missing re-export in any of these modules would only surface at runtime for users.
Suggested: Add minimal unit tests per new module that import the primary public symbol and call it with a fixture.
Proof (missing at unit):tests/unit/install/test_manifest_update.py::test_check_package_conflicts_empty-- proves: The manifest update module is importable and its core conflict-detection logic behaves correctly after extraction from commands/install.py [portability-by-manifest,devx]
assert _check_package_conflicts([]) == set()
This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.
Note
🔒 Integrity filter blocked 7 items
The following items were blocked because they don't meet the GitHub integrity level.
- #1113
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - refactor: tighten stage 1 ruff thresholds #1113
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - #1113
search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - b6fa2de
list_commits: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - 0bd846f
list_commits: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - 985118f
list_commits: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - cb07ab0
list_commits: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
tools:
github:
min-integrity: approved # merged | approved | unapproved | noneGenerated by PR Review Panel for issue #1113 · ● 7.4M · ◷
|
@zeel2104 looks like we got another (small) conflict, and we've got two recommendations from the APM Panel. Let's get these two over the line and merge them. Thanks for the continued effort! |
Head branch was pushed to by a user without write access
b6fa2de to
250b424
Compare
|
@sergio-sisternes-epam Changes:
|
Description
Tightens the Stage 1 Ruff complexity thresholds for the strangler-fig refactor work and trims the main outliers blocking those guardrails.
Changes include:
MCPIntegrator.installby extracting runtime selection, registry install, self-defined install, and summary helpers.pyproject.tomlto the Stage 1 targets.Fixes #1077
Type of change
Testing
Validation run: