Skip to content

refactor: split marketplace commands into package modules#1024

Merged
danielmeppiel merged 18 commits intomicrosoft:mainfrom
shreejaykurhade:refactor/marketplace
Apr 30, 2026
Merged

refactor: split marketplace commands into package modules#1024
danielmeppiel merged 18 commits intomicrosoft:mainfrom
shreejaykurhade:refactor/marketplace

Conversation

@shreejaykurhade
Copy link
Copy Markdown
Contributor

@shreejaykurhade shreejaykurhade commented Apr 28, 2026

Description

Refactors the marketplace CLI implementation by replacing the monolithic src/apm_cli/commands/marketplace.py file with a dedicated src/apm_cli/commands/marketplace/ package using per-command modules.

This improves maintainability and reviewability while preserving existing CLI behavior, import compatibility, and marketplace authoring guardrails.

Fixes #821

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Tested with:

python -m compileall -q src/apm_cli/commands/marketplace src/apm_cli/commands/marketplace_plugin.py

python -m pytest tests/unit/commands/test_marketplace_build.py tests/unit/commands/test_marketplace_check.py tests/unit/commands/test_marketplace_doctor.py tests/unit/commands/test_marketplace_gating.py tests/unit/commands/test_marketplace_init.py tests/unit/commands/test_marketplace_outdated.py tests/unit/commands/test_marketplace_plugin.py tests/unit/commands/test_marketplace_publish.py tests/unit/marketplace/test_marketplace_commands.py tests/unit/marketplace/test_marketplace_validator.py -q

@shreejaykurhade
Copy link
Copy Markdown
Contributor Author

@danielmeppiel @sergio-sisternes-epam please review

@sergio-sisternes-epam sergio-sisternes-epam added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 28, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

@shreejaykurhade good one - please use pr-description-skill to post large refactoring like this next time

@danielmeppiel danielmeppiel removed the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 28, 2026
@microsoft microsoft deleted a comment from github-actions Bot Apr 28, 2026
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 28, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

Architecture is sound and all user-visible behavior is preserved, but one two-line fix is required: subprocess must be imported directly in doctor.py rather than routed through the package __all__.

Required before merge (1 item)

  • [python-architect] stdlib subprocess re-exported in package __all__ and imported by submodule from parent at src/apm_cli/commands/marketplace/doctor.py:11
    • Why: doctor.py line 11 does from . import (..., subprocess), pulling the stdlib module through the parent package instead of importing it directly. __init__.py line 1202 includes "subprocess" in __all__, advertising a stdlib module as part of the marketplace package's public API. This pollutes the package surface, misleads dependency-analysis tooling (e.g. import-linter, deptry), and establishes a precedent for routing stdlib through parent packages -- a pattern that compounds as more modules are extracted.
    • Suggested fix: In doctor.py, replace subprocess in the from . import (...) line with a standalone import subprocess at the top of the file (after import sys). In __init__.py, remove "subprocess" from __all__ (line 1202). The parent module keeps its own import subprocess for the inline consumer commands that still reference it.

Nits (10 items, skip if you want)

  • [python-architect] __init__.py is still 1204 lines -- consumer commands remain inline, making the refactor ~40% complete by line reduction
  • [python-architect] 69-symbol __all__ acts as a re-export facade for the entire marketplace domain layer; submodules could import directly from their actual source modules instead
  • [python-architect] Inconsistent deferred-vs-eager import convention across extracted modules (e.g. build.py eagerly imports BuildOptions but defers MarketplaceBuilder from the same source)
  • [cli-logging-expert] subprocess re-exported through package __init__ instead of direct stdlib import in doctor.py (same root as required finding above)
  • [cli-logging-expert] validate.py lines 60 and 80 use bare click.echo() for blank-line spacing, bypassing CommandLogger quiet-mode contracts (pre-existing)
  • [cli-logging-expert] validate.py line 91 catch-all logger.error(...) omits symbol="error", so output renders without the [x] prefix; inconsistent with all sibling modules (pre-existing)
  • [devx-ux-expert] search is absent from both _consumer_commands and _authoring_commands in MarketplaceGroup.format_commands(), making it invisible in apm marketplace --help (pre-existing gap, opportune moment to add it)
  • [supply-chain-security-expert] subprocess stdlib module explicitly in __all__ signals to tooling that it is an intentional public export of the package; remove it and have doctor.py import directly
  • [oss-growth-hacker] __init__.py still ~1200 lines -- a follow-up "good first issue" to extract consumer commands would complete the story for the next contributor
  • [oss-growth-hacker] Consider naming @shreejaykurhade in the next release notes; structural contributions like this one are easy to make invisible, and acknowledgment compounds contributor retention

CEO arbitration

The panel is unanimous in substance and nearly so in severity: five of six active panelists reviewed the change, three independently flagged the same defect (stdlib subprocess re-exported through the package __all__ and consumed by doctor.py via a relative import), and the remaining two found nothing blocking. The single REQUIRED finding from python-architect is warranted. Advertising subprocess in __all__ pollutes the package's public API surface, misleads static-analysis tooling, and -- critically -- sets a precedent that will compound as the remaining consumer commands are extracted in follow-up work. The fix is genuinely trivial: one direct import added to doctor.py, one string removed from __all__. Asking the contributor to make that two-line change before merge is the right call; it keeps the refactor clean from day one rather than accumulating 'fix later' debt that rarely gets fixed.

Strategically, this PR is exactly the kind of contribution APM needs to cultivate. An external community member (@shreejaykurhade) voluntarily took on a thankless structural refactor in one of our most strategically important subsystems -- marketplace authoring commands -- and delivered a clean, behavior-preserving split that moves 14 files and nets a slight line reduction. The remaining ~1200 lines in __init__.py are a natural follow-up, not a deficiency of this PR. Filing a 'good-first-issue' for that extraction immediately gives the contributor or a newcomer a clear next step. No CHANGELOG entry is needed for a pure refactor. The reject verdict is procedurally correct: merge once the two-line fix lands, then celebrate the contribution.

Dissent resolved: cli-logging-expert flagged the subprocess re-export as a NIT; python-architect flagged it as REQUIRED. The CEO sides with python-architect. This is not a cosmetic preference -- it is an API surface defect that will be inherited by every future module extracted from __init__.py. supply-chain-security-expert also flagged it as a nit but recommended the same remediation, confirming the fix is non-controversial even if severity calibration differed.

Growth/positioning note: Strong community health signal from @shreejaykurhade tackling structural refactor #821 in marketplace authoring. Three actions: (1) Name the contributor in the next release notes. (2) File a follow-up good-first-issue for extracting consumer commands from __init__.py. (3) Fast-track re-review once the two-line fix lands -- do not let a trivial remediation become a contributor-attrition event.


Per-persona findings (full)

Python Architect

classDiagram
    direction TB
    class MarketplaceGroup {
        <<click.Group>>
        +_consumer_commands list
        +_authoring_commands list
        +_authoring_visible() bool
        +format_commands(ctx, formatter)
    }
    class CommandLogger {
        <<Base>>
        +start(msg)
        +progress(msg)
        +success(msg)
        +error(msg)
        +verbose_detail(msg)
    }
    class _OutdatedRow {
        <<ValueObject>>
        +name str
        +current str
        +latest_in_range str
        +status str
    }
    class _CheckResult {
        <<ValueObject>>
        +name str
        +reachable bool
        +version_found bool
        +error str
    }
    class _DoctorCheck {
        <<ValueObject>>
        +name str
        +passed bool
        +detail str
    }
    class marketplace_init {
        <<Package __init__ 1204 lines>>
        +marketplace ClickGroup
        +add() consumer cmd
        +list_cmd() consumer cmd
        +browse() consumer cmd
        +update() consumer cmd
        +remove() consumer cmd
        +search() standalone cmd
        +_load_yml_or_exit()
        +_render_build_error()
        +_render_build_table()
    }
    class build_mod {
        <<ExtractedModule>>
        +build() click cmd
    }
    class check_mod {
        <<ExtractedModule>>
        +check() click cmd
    }
    class doctor_mod {
        <<ExtractedModule -- REQUIRED>>
        +doctor() click cmd
    }
    class outdated_mod {
        <<ExtractedModule>>
        +outdated() click cmd
    }
    class publish_mod {
        <<ExtractedModule>>
        +publish() click cmd
    }
    class plugin_pkg {
        <<ExtractedSubpackage>>
        +package ClickGroup
        +add() click cmd
        +remove() click cmd
        +set_cmd() click cmd
    }
    MarketplaceGroup <|-- marketplace_init : cls=MarketplaceGroup
    marketplace_init *-- build_mod : bottom import
    marketplace_init *-- check_mod : bottom import
    marketplace_init *-- doctor_mod : bottom import
    marketplace_init *-- outdated_mod : bottom import
    marketplace_init *-- publish_mod : bottom import
    marketplace_init *-- plugin_pkg : bottom import
    build_mod ..> marketplace_init : from . import marketplace helpers
    check_mod ..> marketplace_init : from . import marketplace helpers
    doctor_mod ..> marketplace_init : from . import marketplace subprocess
    outdated_mod ..> marketplace_init : from . import marketplace helpers
    publish_mod ..> marketplace_init : from . import marketplace helpers
    build_mod ..> CommandLogger : uses
    check_mod ..> CommandLogger : uses
    doctor_mod ..> CommandLogger : uses
    outdated_mod ..> CommandLogger : uses
    publish_mod ..> CommandLogger : uses
    check_mod ..> _CheckResult : creates
    outdated_mod ..> _OutdatedRow : creates
    doctor_mod ..> _DoctorCheck : creates
Loading
flowchart TD
    A["apm marketplace build --dry-run"] --> B["cli.py imports marketplace group"]
    B --> C["marketplace/__init__.py\nModule load: 46 top-level imports\ndefine MarketplaceGroup, helpers, inline cmds"]
    C --> D["__init__.py bottom imports\nfrom .build import build\nfrom .check import check ..."]
    D --> E["build.py:12\nfrom . import marketplace, helpers, BuildOptions"]
    E --> F["Circular resolved: __init__ already defined\nall symbols before bottom imports execute"]
    F --> G["Click dispatch: MarketplaceGroup\nroutes build to build.build()"]
    G --> H["build.py:24 _require_authoring_flag()"]
    H --> I["build.py:25 CommandLogger marketplace-build"]
    I --> J["build.py:29 _load_yml_or_exit logger"]
    J --> K["build.py:23 Deferred: from . import MarketplaceBuilder"]
    K --> L{"build.py:32 dry_run?"}
    L -->|yes| M["logger.info preview only"]
    L -->|no| N["MarketplaceBuilder.build\nresolves refs, writes marketplace.json"]
    N --> O{"Result?"}
    O -->|success| P["_render_build_table logger, report"]
    O -->|BuildError| Q["_render_build_error logger, exc\nsys.exit 1"]
    O -->|MarketplaceYmlError| R["logger.error str exc\nsys.exit 2"]
    style E fill:#fff3b0,stroke:#d47600
    style K fill:#fff3b0,stroke:#d47600
Loading

Required:

  • stdlib subprocess re-exported in package __all__ and imported by submodule from parent at doctor.py:11 -- see top of comment.

Nits:

  • __init__.py is still 1204 lines; consumer commands (add, list_cmd, browse, update, remove ~330 lines) and all helper/renderer functions (~470 lines) remain inline. Consider a follow-up to co-locate renderer helpers with their command modules.
  • 69-symbol __all__ re-exports the entire marketplace domain layer (BuildOptions, MarketplaceBuilder, PrIntegrator, etc.) solely so submodules can write from . import SomeClass. Submodules could import directly from their canonical source modules, shrinking __all__ to only symbols this package actually owns.
  • Inconsistent deferred-vs-eager import convention: build.py eagerly imports BuildOptions but defers MarketplaceBuilder from the same module; publish.py eagerly imports PublishOutcome but defers MarketplacePublisher. Document the rule (e.g. "defer classes that trigger network/FS at import time") to prevent drift.

CLI Logging Expert

No findings. (All nits below are pre-existing code moved intact.)

Nits:

  • subprocess re-exported through package __init__ instead of direct stdlib import in doctor.py -- the refactor made this coupling explicit in __all__.
  • validate.py lines 60 and 80: bare click.echo() for blank-line spacing bypasses CommandLogger quiet-mode contracts. Pre-existing from original monolith.
  • validate.py line 91: logger.error(f"Failed to validate marketplace: {e}") omits symbol="error", rendering without the [x] prefix. Every other top-level catch-all in the extracted modules passes symbol="error". Pre-existing.

DevX UX Expert

No required findings. All user-visible CLI behavior (help text, command names, arguments, options, error messages) is faithfully preserved. Compatibility shim in marketplace_plugin.py correctly re-exports all symbols.

Nits:

  • search is absent from both _consumer_commands and _authoring_commands in MarketplaceGroup.format_commands(), so it does not appear in apm marketplace --help. Pre-existing gap; this refactor was an opportune moment to add "search" to _consumer_commands.

Supply Chain Security Expert

No required findings. All security-critical code paths (path traversal guards via validate_path_segments, subprocess calls with fixed command arrays and no shell=True, AuthResolver probe) were moved intact, not modified.

Nits:

  • subprocess stdlib module in __all__ signals to tooling that it is an intentional public API export. Remove it from __all__ and have doctor.py use a direct import subprocess -- standard Python idiom, avoids any appearance that the marketplace package mediates subprocess access.

Auth Expert

Inactive -- PR #1024 restructures src/apm_cli/commands/marketplace.py into a package without modifying any auth-specific files (auth.py, token_manager.py, azure_cli.py, github_downloader.py, github_host.py, etc.); the AuthResolver reference in doctor.py is an unchanged move, so no authentication behavior is affected.

OSS Growth Hacker

No required findings. No CHANGELOG entry needed for a pure refactor.

Nits:

  • __init__.py still ~1200 lines; the refactor extracted authoring commands but consumer commands plus shared helpers remain. A follow-up "good first issue" to extract add, list, browse, update, remove would complete the story and create a natural onramp for the next community contributor.
  • Consider naming @shreejaykurhade in the next release notes under a "Maintenance" heading. Structural contributions by external contributors are easy to make invisible; one line costs nothing and signals that housekeeping PRs are valued -- exactly the contributor-funnel behavior APM needs to reinforce.

Verdict computed deterministically: 1 required finding across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Generated by PR Review Panel for issue #1024 · ● 22.9M ·

@github-actions github-actions Bot added panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push. and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 28, 2026
Copilot AI review requested due to automatic review settings April 29, 2026 00:40
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 marketplace CLI command implementation from a single large module into a src/apm_cli/commands/marketplace/ package with per-command modules, while keeping import compatibility via wrappers/re-exports.

Changes:

  • Split apm marketplace {init,build,validate,check,outdated,doctor,publish} into dedicated modules under src/apm_cli/commands/marketplace/.
  • Moved the apm marketplace package {add,set,remove} subgroup into src/apm_cli/commands/marketplace/plugin/, keeping marketplace_plugin.py as a compatibility wrapper.
  • Updated integration test troubleshooting docs to point to the new module locations.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/integration/marketplace/README.md Updates troubleshooting guidance to reference the new marketplace command module layout.
src/apm_cli/commands/marketplace_plugin.py Compatibility wrapper re-exporting the moved package subgroup.
src/apm_cli/commands/marketplace/init.py Keeps Click group wiring + shared helpers; re-exports split command modules.
src/apm_cli/commands/marketplace/build.py Extracted apm marketplace build into its own module.
src/apm_cli/commands/marketplace/check.py Extracted apm marketplace check into its own module.
src/apm_cli/commands/marketplace/doctor.py Extracted apm marketplace doctor into its own module.
src/apm_cli/commands/marketplace/init.py Extracted apm marketplace init into its own module.
src/apm_cli/commands/marketplace/outdated.py Extracted apm marketplace outdated into its own module.
src/apm_cli/commands/marketplace/publish.py Extracted apm marketplace publish into its own module.
src/apm_cli/commands/marketplace/validate.py Extracted apm marketplace validate into its own module.
src/apm_cli/commands/marketplace/plugin/init.py New package subgroup wiring + shared helper functions for package commands.
src/apm_cli/commands/marketplace/plugin/add.py Extracted apm marketplace package add into its own module.
src/apm_cli/commands/marketplace/plugin/set.py Extracted apm marketplace package set into its own module.
src/apm_cli/commands/marketplace/plugin/remove.py Extracted apm marketplace package remove into its own module.

Comment on lines +78 to +83
# Version-based — no ref resolution needed.
if version is not None:
return None

# Already a concrete SHA — store as-is.
if ref is not None and _SHA_RE.match(ref):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This file uses Unicode em dash characters (U+2014) in comments (e.g., "Version-based ..." / "Already a concrete SHA ..."). The repo encoding rule requires printable ASCII only for source files; please replace the em dashes with ASCII '-' or '--' to avoid Windows cp1252 encoding issues.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +122
# Non-HEAD, non-SHA ref — check whether it is a branch name.
resolver = RefResolver()
try:
remote_refs = resolver.list_remote_refs(source)
except (GitLsRemoteError, OfflineMissError):
# Cannot verify — store as-is but warn the user.
logger.warning(
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

Non-ASCII Unicode em dash characters (U+2014) are used in comments here ("Non-HEAD, non-SHA ref ..." and "Cannot verify ..."). Please switch these to ASCII '-' / '--' so the source stays within printable ASCII per the repo encoding rule.

Copilot uses AI. Check for mistakes.
)
return remote_ref.sha

# Not a branch — tag or unknown ref; store as-is.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This comment contains a Unicode em dash (U+2014): "Not a branch ... tag or unknown ref". Please replace it with ASCII punctuation to keep the source file printable-ASCII-only.

Suggested change
# Not a branchtag or unknown ref; store as-is.
# Not a branch, tag, or unknown ref; store as-is.

Copilot uses AI. Check for mistakes.
@shreejaykurhade
Copy link
Copy Markdown
Contributor Author

@danielmeppiel done

@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push. labels Apr 29, 2026
shreejaykurhade and others added 3 commits April 29, 2026 15:17
# Conflicts:
#	src/apm_cli/commands/marketplace/__init__.py
…pline

Address review-panel REQUIRED finding plus architect feedback:

REQUIRED FIX (search alias regression):
- Remove 'search' from MarketplaceGroup._consumer_commands and drop the
  marketplace.add_command(search) call. 'apm search' remains the canonical
  top-level command (registered in cli.py); the new 'apm marketplace search'
  alias was an unintentional surface added by the refactor split.
- Update tests/unit/commands/test_marketplace_gating.py to reflect that
  'search' is not part of the marketplace group's consumer command set.

IMPORT DISCIPLINE (architect feedback):
- Submodules (build, check, doctor, init, outdated, publish, validate,
  plugin/{add,remove,set}) now import domain types from their canonical
  source modules (...core.command_logger, ...marketplace.builder,
  ...marketplace.errors, etc.) instead of re-importing them via the
  package __init__. Drop redundant lazy 'from . import X' calls inside
  command bodies.
- Package __init__ keeps eager re-exports (used by test mock.patch and
  by the helpers that still live in __init__) but the submodule code
  paths no longer rely on them.
- Remove dead 'import subprocess' from package __init__.

CLEANUP:
- Delete src/apm_cli/commands/marketplace_plugin.py compatibility shim.
  Its sole consumer (tests/unit/commands/test_marketplace_plugin.py)
  now imports from apm_cli.commands.marketplace.plugin directly.
- Update mock.patch paths in unit + integration tests to point at the
  canonical submodule namespace (e.g. marketplace.build.MarketplaceBuilder
  instead of marketplace.MarketplaceBuilder).

Verified: 6702 unit tests pass; 'apm marketplace --help' lists the
correct consumer set (no 'search'); 'from apm_cli.commands.marketplace
import marketplace, search' still works.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel
Copy link
Copy Markdown
Collaborator

Update: review-panel feedback addressed (commit 7299f55)

Fixed the REQUIRED finding from the local review panel and the import-discipline issues flagged by python-architect.

REQUIRED fix: search alias regression

  • Removed search from MarketplaceGroup._consumer_commands and dropped the marketplace.add_command(search) call.
  • apm search remains the canonical top-level command (still registered in cli.py); apm marketplace search was an unintentional new alias introduced by the package split.
  • Updated tests/unit/commands/test_marketplace_gating.py to match.

Import discipline (architect feedback)

  • Submodules (build, check, doctor, init, outdated, publish, validate, plugin/{add,remove,set}) now import domain types from their canonical source modules (core.command_logger, marketplace.builder, marketplace.errors, etc.) instead of re-importing them via marketplace/__init__.py.
  • Dropped the lazy from . import X pattern inside command bodies (no more import-on-call-time).
  • Package __init__ keeps eager re-exports for test mock.patch compatibility and for helpers that still live there, but submodule code paths no longer depend on the re-export indirection.
  • Removed dead import subprocess from package __init__.

Cleanup

  • Deleted the src/apm_cli/commands/marketplace_plugin.py compatibility shim. Its sole consumer (test_marketplace_plugin.py) now imports from apm_cli.commands.marketplace.plugin directly.
  • Updated mock.patch paths in 6 test files to point at the canonical submodule namespace (e.g. marketplace.build.MarketplaceBuilder instead of marketplace.MarketplaceBuilder).

Verification

  • uv run --extra dev pytest tests/unit tests/test_console.py6702 passed, 27 subtests passed
  • Smoke: apm marketplace --help lists add, list, browse, update, remove, validate (no search); apm search still works.

Deliberately punted to follow-up (architect's call, pragmatic for this PR)

  • Extracting consumer commands (add, list_cmd, browse, update, remove, search) into their own submodules. Asymmetric split (authoring extracted, consumer not) is acceptable temporarily.
  • Extracting render helpers (_render_*) into a dedicated rendering module.
  • Minor PEP 8 spacing nits in submodule decorators.

These are tracked as low-priority follow-ups; not blocking for this refactor's goal.

@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 29, 2026
…/marketplace

# Conflicts:
#	src/apm_cli/commands/marketplace/__init__.py
#	src/apm_cli/commands/marketplace_plugin.py
#	tests/unit/commands/test_marketplace_check.py
@shreejaykurhade
Copy link
Copy Markdown
Contributor Author

shreejaykurhade commented Apr 29, 2026

@danielmeppiel please check as ci cd are failing and merge are still happening. it's not feasible for me to maintain this refactor as most changes are occuring in these files. Would love if you approve this earlier.

# Conflicts:
#	src/apm_cli/commands/marketplace/__init__.py
#	src/apm_cli/commands/marketplace_plugin.py
#	tests/unit/commands/test_marketplace_build.py
#	tests/unit/commands/test_marketplace_gating.py
@shreejaykurhade
Copy link
Copy Markdown
Contributor Author

@danielmeppiel please review

@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 29, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

@danielmeppiel please review

We had some problems with the agentic PR review, it should come in now. We do best effort on reviewing and want to get this one in. Thank you!

@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

Three specialists independently flagged the gitignore regression; a misleading no-op guard and a help-text regression add two further required fixes before merge.

Required before merge (6 items)

  • [python-architect] _require_authoring_flag() is a silent no-op imported and called as a guard in all 7 authoring submodules but returns None, misleading future readers into thinking a gate exists at src/apm_cli/commands/marketplace/__init__.py:200

    • Why: If the guard previously enforced a feature-flag, that gate was silently removed during extraction. If it was always dead, the 7 import+call sites are noise that violates the "no misleading abstractions" design standard.
    • Suggested fix: Restore the guard logic (env-var check or CLI option check), or delete the function and remove all _require_authoring_flag() call sites from check.py, doctor.py, init.py, migrate.py, outdated.py, publish.py, and plugin/init.py. A TODO comment with an issue reference is the minimum if deferring.
  • [python-architect] __init__.py is still 1277 LOC; consumer commands (add, list, browse, update, remove, search) and all _render_* helpers were not extracted at src/apm_cli/commands/marketplace/__init__.py

    • Why: The PR title claims "split marketplace commands into package modules" but leaves consumer commands and render helpers inline in __init__. The dependency arrow from sibling submodules back into __init__ for _render_check_table, _render_doctor_table, etc. inverts the expected layering. The stated goal of the refactor is half-done.
    • Suggested fix: Extract add/list/browse/update/remove/search into consumer submodules. Move _render_* helpers and private dataclasses (_OutdatedRow, _CheckResult, _DoctorCheck) to the submodules that own them or a shared _render.py. __init__.py should shrink to group wiring and shared utilities.
  • [cli-logging-expert] Warning text regression: _check_gitignore_for_marketplace_json now says "Both marketplace.yml and marketplace.json must be tracked in git" but the canonical config is apm.yml at src/apm_cli/commands/marketplace/__init__.py

    • Why: The project migrated from marketplace.yml to apm.yml. This warning actively sends users to find a file that no longer exists as the primary artifact. Message rule: "Name the thing" -- name the correct file.
    • Suggested fix: Restore: "Both apm.yml and the generated marketplace.json must be tracked in git. Remove the .gitignore rule."
  • [devx-ux-expert] Gitignore warning message names "marketplace.yml" instead of "apm.yml", giving users wrong recovery guidance (same bug as above, flagged independently via UX lens)

    • Why: Violates the "Failure mode is the product" principle -- every error must name what failed correctly and give one concrete next action. A user following this warning will take the wrong action.
    • Suggested fix: Restore the original wording referencing apm.yml.
  • [devx-ux-expert] apm marketplace init --help drops the scaffold-on-missing-apm.yml affordance, hiding a key first-run behavior from new users at src/apm_cli/commands/marketplace/init.py

    • Why: The docs (cli-commands.md) explicitly document "If apm.yml is absent, a minimal one is scaffolded first" as a first-run affordance. Removing this from help text surprises users in an empty directory. Anti-pattern: help text written for maintainers, not users.
    • Suggested fix: Restore the scaffold note: "Add a 'marketplace:' block to apm.yml (scaffolds apm.yml if missing)"
  • [oss-growth-hacker] Warning message referencing "marketplace.yml" sends migrating users to a dead end, actively undermining the migration funnel that the migrate command exists to serve

    • Why: A user who follows the warning is working against the tool's own migration story. This damages the legacy->apm.yml conversion path at exactly the moment users are most likely to encounter it.
    • Suggested fix: Same one-line fix as above -- restore "apm.yml" in the warning text.

Nits (11 items, skip if you want)

  • [python-architect] Gitignore warning wording changed silently inside a structural refactor commit -- harder to catch in review and harder to bisect at src/apm_cli/commands/marketplace/__init__.py:234
  • [python-architect] Mid-module import (from .plugin import package) breaks PEP 8 import ordering with no explanatory comment at src/apm_cli/commands/marketplace/__init__.py:211
  • [python-architect] import builtins + list = builtins.list is an unnecessary shadow workaround; the function is already named list_cmd so the builtin shadow serves no purpose at src/apm_cli/commands/marketplace/__init__.py:55
  • [cli-logging-expert] blank_line() in CommandLogger calls _rich_echo("") directly without a quiet-mode guard at src/apm_cli/core/command_logger.py
  • [cli-logging-expert] init.py imports _rich_panel inside a try/except at call-site; import failures silently produce structurally different output with no developer signal at src/apm_cli/commands/marketplace/init.py
  • [devx-ux-expert] _require_authoring_flag() stub has no TODO or issue reference to signal intent or timeline at src/apm_cli/commands/marketplace/__init__.py
  • [devx-ux-expert] publish.py error "marketplace.json not found. Run 'apm pack' first." does not name the expected path; users who ran apm pack with a custom output path will not understand the error at src/apm_cli/commands/marketplace/publish.py
  • [supply-chain-security-expert] doctor.py network check probes github.com/git/git.git -- a hardcoded third-party URL -- which produces a false negative in private-registry-only environments at src/apm_cli/commands/marketplace/doctor.py:66
  • [supply-chain-security-expert] --subdir value is stored in marketplace.yml config without path-traversal validation at write time; validation should happen at input, not deferred to install at src/apm_cli/commands/marketplace/plugin/add.py:29
  • [oss-growth-hacker] No CHANGELOG entry for this refactor; "split marketplace commands into package modules" lowers contributor surface area and is worth one line under an Internal/Refactoring section in CHANGELOG.md
  • [oss-growth-hacker] The migrate command's new discoverability via _authoring_commands is an unannounced growth beat for the migration story -- worth calling out in the PR description and CHANGELOG

CEO arbitration

The panel's most urgent finding is a fact-regression, not an opinion: three independent panelists (cli-logging-expert, devx-ux-expert, oss-growth-hacker) independently identified that the gitignore warning now names "marketplace.yml" instead of "apm.yml". This is not a style preference -- it is an actively wrong recovery instruction that will send users hunting for a file that no longer exists. The triple-flagging constitutes the strongest possible panel consensus short of unanimity, and the fix is a one-line string restore. This alone is sufficient to block the PR as-submitted.

On scope: the python-architect's finding that __init__.py remains 1277 LOC is legitimate debt, but it is not fairly chargeable to this PR as a blocking defect unless the PR claims full extraction. The right disposition is to open a follow-up issue for extracting consumer commands before merge. However, _require_authoring_flag() is different: if it was once a feature-gate and that gate was silently removed during extraction, that is a behavioral regression. If it was always dead code, delete it rather than import it across seven call sites. Either resolution is a required pre-merge action.

The devx-ux-expert's finding on apm marketplace init --help hiding the scaffold affordance is a real DX regression for new users. First-run ergonomics are a top-of-funnel concern; a user in an empty directory who reads help text that omits this behavior may assume the tool cannot scaffold and abandon. No specialist dissented on this point.

Dissent resolved: The only material dissent is on severity of _require_authoring_flag(). python-architect flagged it as required; devx-ux-expert flagged it as a nit. I side with the python-architect: a function named as a guard but returning None with no annotation is misleading regardless of its history. The no-op as shipped blocks.

Growth/positioning note: The migrate command's improved discoverability in this refactor is a small but real growth beat for the legacy->apm.yml migration story. Fix the wrong-filename warning first -- a user who encounters the warning before running migrate is being actively misdirected. Once the regression is patched, a one-line CHANGELOG entry calling out migrate discoverability amplifies the migration funnel without extra work.


Per-persona findings (full)

Python Architect

classDiagram
    direction TB
    class MarketplaceInit {
        <<Package __init__ 1277 LOC>>
        +MarketplaceGroup
        +marketplace : click.Group
        +_load_config_or_exit()
        +_check_gitignore_for_marketplace_json()
        +_require_authoring_flag()
        +_render_check_table()
        +_render_doctor_table()
        +_render_publish_plan()
        +_render_publish_summary()
        +add() +list_cmd() +browse()
        +update() +remove() +search()
    }
    class CheckModule {
        <<SubModule check.py>>
        +check()
    }
    class DoctorModule {
        <<SubModule doctor.py>>
        +doctor()
    }
    class InitModule {
        <<SubModule init.py>>
        +init()
    }
    class MigrateModule {
        <<SubModule migrate.py>>
        +migrate()
    }
    class OutdatedModule {
        <<SubModule outdated.py>>
        +outdated()
    }
    class PublishModule {
        <<SubModule publish.py>>
        +publish()
    }
    class ValidateModule {
        <<SubModule validate.py>>
        +validate()
    }
    class PluginPackage {
        <<SubPackage plugin>>
        +package : click.Group
        +add() +set() +remove()
    }
    class CommandLogger {
        <<Base>>
        +start() +progress() +success()
        +warning() +error()
        +blank_line()
    }
    class MarketplaceInit:::touched
    class CheckModule:::touched
    class DoctorModule:::touched
    class InitModule:::touched
    class MigrateModule:::touched
    class OutdatedModule:::touched
    class PublishModule:::touched
    class ValidateModule:::touched
    class PluginPackage:::touched
    class CommandLogger:::touched
    MarketplaceInit *-- MarketplaceGroup : defines
    CheckModule ..> MarketplaceInit : imports marketplace + helpers
    DoctorModule ..> MarketplaceInit : imports marketplace + helpers
    InitModule ..> MarketplaceInit : imports marketplace + helpers
    MigrateModule ..> MarketplaceInit : imports marketplace + helpers
    OutdatedModule ..> MarketplaceInit : imports marketplace + helpers
    PublishModule ..> MarketplaceInit : imports marketplace + helpers
    ValidateModule ..> MarketplaceInit : imports marketplace + helpers
    PluginPackage ..> MarketplaceInit : imports marketplace
    MarketplaceInit ..> PluginPackage : late-imports package
    CheckModule ..> CommandLogger : instantiates
    DoctorModule ..> CommandLogger : instantiates
    note for MarketplaceInit "REQUIRED: still 1277 LOC. Consumer commands and render helpers not yet extracted."
    note for CheckModule "_require_authoring_flag() imported but function is a no-op stub"
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A(["$ apm marketplace check"]) --> B["click dispatches to check.py::check()"]
    B --> C["_require_authoring_flag()\nmarketplace/__init__.py:200\nDEAD NOP - returns None"]
    C --> D["CommandLogger('marketplace-check')\ncore/command_logger.py"]
    D --> E["_load_config_or_exit(logger)\nmarketplace/__init__.py:144\nIO reads apm.yml or marketplace.yml"]
    E -->|MarketplaceYmlError| F["logger.error() + sys.exit(1 or 2)"]
    E -->|ok| G["_warn_duplicate_names(logger, yml)\nmarketplace/__init__.py:169"]
    G --> H{"--offline flag?"}
    H -->|yes| I["schema-only check path\nIO read marketplace.yml"]
    H -->|no| J["RefResolver.list_remote_refs()\nNET git ls-remote per entry"]
    J -->|GitLsRemoteError| K["_render_check_table with error result\nmarketplace/__init__.py:804"]
    J -->|ok| L["_extract_tag_versions()\nmarketplace/__init__.py:724\nsatisfies_range() semver check"]
    L --> K
    I --> K
    K --> M["sys.exit(0) or sys.exit(1)"]
Loading

Design patterns

  • Used in this PR: Facade -- marketplace/__init__.py acts as a facade exposing shared helpers and the marketplace Click group to all submodules; submodules import the group and helpers through the facade rather than depending on each other.
  • Used in this PR: Template Method (implicit) -- each authoring submodule follows the same skeleton: call _require_authoring_flag(), instantiate CommandLogger, call _load_config_or_exit(), run domain logic, call a _render_* helper. The skeleton is not enforced by a base class.
  • Pragmatic suggestion: extract the authoring-command skeleton into a thin run_authoring_command(name, verbose, fn) helper in __init__.py -- this would eliminate 7 identical preambles, make the no-op call disappear naturally, and make the pattern visible. Net gain: approximately 50 LOC removed without adding a full base class.

Required findings:

  1. _require_authoring_flag() is a silent no-op guard (see Required section above)
  2. __init__.py still 1277 LOC; consumer commands and render helpers not extracted (see Required section above)

Nits:

  • Gitignore warning wording changed silently in a structural refactor commit
  • Mid-module import breaks PEP 8 ordering with no explanatory comment
  • import builtins + shadow unnecessary since list_cmd already avoids the naming collision

CLI Logging Expert

Required:

  • Warning text regression: _check_gitignore_for_marketplace_json names "marketplace.yml" instead of "apm.yml" -- actively wrong recovery instruction (see Required section above)

Nits:

  • blank_line() calls _rich_echo("") without a quiet-mode guard; should check quiet mode like other CommandLogger methods
  • init.py imports _rich_panel inside a try/except at call-site; silent fallback produces structurally different output with no signal

DevX UX Expert

Required:

  • Gitignore warning names wrong file -- wrong recovery guidance (see Required section above)
  • apm marketplace init --help drops scaffold affordance -- hides key first-run behavior (see Required section above)

Nits:

  • _require_authoring_flag() stub has no TODO or issue reference
  • publish.py error omits expected path for marketplace.json; users who passed --output will be confused

Supply Chain Security Expert

No findings.

Nits:

  • doctor.py network check probes github.com/git/git.git -- hardcoded third-party URL produces false negatives in private-registry-only environments
  • --subdir stored without path-traversal validation at write time; consider validating against path_security.validate_path_segments at input

Auth Expert

Inactive -- No auth files touched; PR refactors marketplace.py into a package with only import-path depth changes (..core.auth -> ...core.auth), no token or credential logic changed.

OSS Growth Hacker

Required:

  • Warning message referencing "marketplace.yml" sends migrating users to a dead end, undermining the migration funnel (see Required section above)

Nits:

  • No CHANGELOG entry for a contributor-experience improvement worth calling out; add one line under Internal/Refactoring
  • migrate command's improved discoverability is an unannounced growth beat; worth a PR description note and CHANGELOG line

Verdict computed deterministically: 6 required findings across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1024 · ● 3.2M ·

@github-actions github-actions Bot added panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push. and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 29, 2026
…help text and warning

- Delete _require_authoring_flag() no-op and remove all 7 import+call sites
  (check, doctor, init, migrate, outdated, publish, plugin/__init__).
- Restore 'apm marketplace init' help text scaffold affordance:
  '(scaffolds apm.yml if missing)'.
- Fix gitignore warning to reference apm.yml instead of marketplace.yml,
  matching the post-migration canonical config.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@shreejaykurhade
Copy link
Copy Markdown
Contributor Author

All the test are passing. let's see how this goes.

@danielmeppiel danielmeppiel enabled auto-merge April 29, 2026 22:35
@danielmeppiel danielmeppiel disabled auto-merge April 30, 2026 05:18
@danielmeppiel danielmeppiel merged commit 29658b9 into microsoft:main Apr 30, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: split commands/marketplace.py into per-command modules

5 participants