feat(marketplace): fold marketplace.yml into apm.yml's 'marketplace:' block (closes #722, implements #1036)#1038
Conversation
Marketplace authoring config now lives inside apm.yml under a top-level 'marketplace:' key instead of a separate marketplace.yml file. The compiled marketplace.json (Anthropic spec) is unchanged. Legacy marketplace.yml continues to work with a deprecation warning; if both files are present, commands hard-error. Schema layer - Rename MarketplaceYml -> MarketplaceConfig (alias kept for back-compat). - Add load_marketplace_from_apm_yml() and load_marketplace_from_legacy_yml() built on a shared _build_config() helper with a default_output param so legacy keeps 'marketplace.json' while apm.yml block defaults to '.claude-plugin/marketplace.json'. - Allow local-path package sources matching '^\./.*$' via LOCAL_SOURCE_RE. is_local entries skip the version/ref requirement and skip git resolution. - Add description, homepage, is_local on PackageEntry. - Add source_path, is_legacy, name/description/version_overridden on the config so the builder can decide what to emit at the top level of marketplace.json. Migration module (new) - ConfigSource enum and detect_config_source() raise on both-files-present. - load_marketplace_config() smart loader for commands, with deprecation warning when legacy marketplace.yml is the source. - migrate_marketplace_yml() folds legacy into the apm.yml block via ruamel.yaml round-trip, supports --dry-run (returns unified diff) and --force (overwrite existing block). - detect_inheritance_conflicts() flags name/description/version mismatches. Builder layer - Add MarketplaceBuilder.from_config() classmethod for caller-loaded configs; legacy constructor preserved. - _load_yml() is shape-aware: paths ending in apm.yml go through the apm.yml loader. - Local packages emit 'source' as a plain string and use the entry's description/homepage/version directly. Top-level description/version are omitted when not explicitly overridden in the marketplace block. - _compute_diff() handles string-shaped sources. Editor layer - yml_editor detects file shape via _is_apm_yml_with_marketplace and navigates data['marketplace']['packages'] for apm.yml or data['packages'] for legacy. _validate_after_write picks the right loader; _validate_source uses allow_current_dir=True. Commands layer - New _load_config_or_exit() helper used by build, check, outdated, publish. - 'apm marketplace init' injects a 'marketplace:' block into an existing apm.yml; otherwise scaffolds legacy marketplace.yml. - 'apm marketplace migrate' (new) with --force and --dry-run. - 'apm marketplace doctor' Check 5 surfaces 'run migrate' when legacy file is present. - marketplace_plugin._yml_path / _ensure_yml_exists detect shape and hard-error when both files exist. - 'apm init --marketplace' flag appends a marketplace block to the newly created apm.yml. Init template - render_marketplace_block(owner) returns an ASCII YAML snippet for the apm.yml block. render_marketplace_yml_template kept for legacy paths. Tests - New: tests/unit/marketplace/test_apm_yml_marketplace_loader.py, tests/unit/marketplace/test_migration_detection.py, tests/unit/marketplace/test_local_path_compose.py, tests/unit/commands/test_marketplace_migrate.py. - Updated: test_yml_schema.py (relaxed regex), test_marketplace_build.py (error wording), test_marketplace_check.py (loader patch target), test_marketplace_doctor.py (wording). - Full unit suite: 6749 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Anthropic spec treats tags as optional pass-through. The azure-skills canonical .claude-plugin/marketplace.json omits tags when empty; emitting 'tags: []' caused a 1-line diff in round-trip validation. Validated: marketplace.json generated from microsoft/azure-skills' apm.yml (with marketplace: block) now matches the existing .claude-plugin/marketplace.json byte-for-byte. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per design.md §4.1, 'apm marketplace init' must scaffold apm.yml when absent and inject the 'marketplace:' block into it -- never fall back to creating standalone marketplace.yml files. The legacy fallback path created a divergent surface that defeated the fold. Rewrite the 22 init tests to assert the new behavior: apm.yml is always the target, --force overwrites the existing marketplace block, --name sets the apm.yml top-level name, --owner sets marketplace.owner.name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three findings from the cli-logging-expert review of the fold-into-
apm.yml CLI surface:
- BLOCKER: 'apm marketplace migrate' rejected '--yes' even though
sibling commands ('remove', 'publish') accept it. Add --yes/-y as
an alias for --force on migrate.
- HIGH: 'apm marketplace init' in an empty repo emitted two [+] lines
('Created apm.yml' + 'Added marketplace block') for one atomic
outcome. Collapse to a single line:
'[+] Created apm.yml with marketplace block'. The injection-only
path (existing apm.yml) keeps its original message.
- HIGH: re-running 'apm marketplace init' against an apm.yml that
already has a marketplace block emitted [x] (error symbol) for what
is a guard, not a failure. Switch to logger.warning + [!] symbol.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR consolidates marketplace authoring configuration into a single apm.yml surface by introducing a top-level marketplace: block (with one-release legacy marketplace.yml support), adds a migration path, and expands package source support to include local ./... paths while keeping the generated marketplace.json Anthropic-compliant.
Changes:
- Add a shape-aware marketplace config loader (
apm.ymlblock vs legacymarketplace.yml) plus a newapm marketplace migratecommand. - Update marketplace build/compose to support local-path packages and conditionally omit empty fields (e.g., tags; inherited top-level description/version).
- Rewrite/extend unit tests to cover apm.yml-block loading, local-path compose, and source detection/migration behavior.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/marketplace/yml_schema.py |
Introduces MarketplaceConfig, apm.yml-block loader, local ./... sources, new defaults/override flags. |
src/apm_cli/marketplace/migration.py |
Adds config-source detection, smart loader w/ deprecation warning, and legacy->apm.yml migration helper. |
src/apm_cli/marketplace/builder.py |
Handles local-path entries, conditional field emission, and output resolution against project root. |
src/apm_cli/marketplace/yml_editor.py |
Makes the YAML editor shape-aware so it can operate on either legacy root keys or apm.yml marketplace: container. |
src/apm_cli/marketplace/init_template.py |
Adds a new rendered snippet for the apm.yml marketplace: block template. |
src/apm_cli/marketplace/__init__.py |
Re-exports new config/loader symbols. |
src/apm_cli/commands/marketplace.py |
Updates init/build/check/outdated/doctor/publish to use the smart loader; adds migrate; changes messaging. |
src/apm_cli/commands/marketplace_plugin.py |
Updates package-subcommands to locate the active config (apm.yml block vs legacy file) and hard-error on both. |
src/apm_cli/commands/init.py |
Adds apm init --marketplace to seed apm.yml with a marketplace authoring block. |
tests/unit/marketplace/test_apm_yml_marketplace_loader.py |
New tests for inheritance/override semantics in apm.yml marketplace block loader. |
tests/unit/marketplace/test_migration_detection.py |
New tests for config-source detection and smart loader behavior. |
tests/unit/marketplace/test_local_path_compose.py |
New tests for local-path package resolution + JSON compose output shape. |
tests/unit/marketplace/test_yml_schema.py |
Updates schema tests for local-path source acceptance and .. traversal rejection. |
tests/unit/commands/test_marketplace_init.py |
Rewritten tests reflecting apm.yml-block initialization behavior. |
tests/unit/commands/test_marketplace_migrate.py |
New CLI tests for the migration command. |
tests/unit/commands/test_marketplace_build.py |
Updates assertions/messages for new “config” wording. |
tests/unit/commands/test_marketplace_check.py |
Updates patches/mocks to use load_marketplace_config. |
tests/unit/commands/test_marketplace_doctor.py |
Updates expected doctor output text for new config naming. |
Copilot's findings
- Files reviewed: 18/18 changed files
- Comments generated: 12
| @@ -687,17 +687,29 @@ def test_owner_not_a_mapping(self, tmp_path: Path): | |||
| load_marketplace_yml(yml) | |||
|
|
|||
| def test_source_dot_traversal(self, tmp_path: Path): | |||
There was a problem hiding this comment.
This test no longer checks single-dot traversal; it validates that local-path sources like './acme' are accepted. Consider renaming the test function to reflect its new purpose (e.g. test_local_source_accepted) so failures are easier to interpret.
| def test_source_dot_traversal(self, tmp_path: Path): | |
| def test_local_source_accepted(self, tmp_path: Path): |
| if not no_gitignore_check: | ||
| _check_gitignore_for_marketplace_json(logger) | ||
|
|
There was a problem hiding this comment.
The init flow now calls _check_gitignore_for_marketplace_json(), but that helper's warning text still references tracking marketplace.yml. Since authoring config moved into apm.yml, please update the helper message (and any related guidance) to reference apm.yml + the generated marketplace.json path.
| _MARKETPLACE_BLOCK_TEMPLATE = """\ | ||
| # Marketplace authoring config (APM-only). | ||
| # Run 'apm marketplace build' to compile this block to .claude-plugin/marketplace.json. | ||
| # |
There was a problem hiding this comment.
This module-level docstring still implies the file only renders a standalone marketplace.yml scaffold, but it now also owns the apm.yml marketplace: block template. Consider updating the module docstring to reflect both templates so readers don't miss render_marketplace_block().
| from .yml_schema import ( | ||
| LOCAL_SOURCE_RE, | ||
| SOURCE_RE, | ||
| load_marketplace_from_apm_yml, | ||
| load_marketplace_yml, | ||
| ) |
There was a problem hiding this comment.
LOCAL_SOURCE_RE is imported but never used here, which will fail unused-import linting. Either remove the import or use it to drive allow_current_dir behavior in _validate_source().
| from ..marketplace.ref_resolver import RefResolver, RemoteRef | ||
| from ..marketplace.semver import SemVer, parse_semver, satisfies_range | ||
| from ..marketplace.migration import ( | ||
| DEPRECATION_MESSAGE, |
There was a problem hiding this comment.
DEPRECATION_MESSAGE is imported but never used in this file; please drop the unused import to keep linting clean.
| DEPRECATION_MESSAGE, |
| """Return ``True`` when *apm_yml_path* exists and has ``marketplace:``.""" | ||
| if not apm_yml_path.exists(): | ||
| return False | ||
| try: | ||
| text = apm_yml_path.read_text(encoding="utf-8") | ||
| data = yaml.safe_load(text) | ||
| except (OSError, yaml.YAMLError): | ||
| return False | ||
| return isinstance(data, dict) and "marketplace" in data and \ | ||
| data["marketplace"] is not None | ||
|
|
||
|
|
There was a problem hiding this comment.
_has_marketplace_block() returns False on YAML parse errors, which makes detect_config_source()/load_marketplace_config() report "No marketplace config" even when apm.yml exists but is malformed. Consider surfacing a MarketplaceYmlError for unreadable apm.yml (or treating parse errors as "apm.yml present" so the real loader error is shown) to avoid misleading guidance.
| """Return ``True`` when *apm_yml_path* exists and has ``marketplace:``.""" | |
| if not apm_yml_path.exists(): | |
| return False | |
| try: | |
| text = apm_yml_path.read_text(encoding="utf-8") | |
| data = yaml.safe_load(text) | |
| except (OSError, yaml.YAMLError): | |
| return False | |
| return isinstance(data, dict) and "marketplace" in data and \ | |
| data["marketplace"] is not None | |
| """Return ``True`` when *apm_yml_path* has a non-null ``marketplace:``. | |
| Missing files and valid YAML without a top-level ``marketplace`` block | |
| return ``False``. Read failures and YAML parse errors raise | |
| :class:`MarketplaceYmlError` so callers do not mistake malformed | |
| ``apm.yml`` for an absent marketplace configuration. | |
| """ | |
| if not apm_yml_path.exists(): | |
| return False | |
| try: | |
| text = apm_yml_path.read_text(encoding="utf-8") | |
| except OSError as exc: | |
| raise MarketplaceYmlError( | |
| f"Could not read {apm_yml_path.name}: {exc}" | |
| ) from exc | |
| try: | |
| data = yaml.safe_load(text) | |
| except yaml.YAMLError as exc: | |
| raise MarketplaceYmlError( | |
| f"Invalid YAML in {apm_yml_path.name}: {exc}" | |
| ) from exc | |
| return ( | |
| isinstance(data, dict) | |
| and "marketplace" in data | |
| and data["marketplace"] is not None | |
| ) |
| apm_text = apm_path.read_text(encoding="utf-8") | ||
| apm_data = rt.load(apm_text) | ||
|
|
||
| if "marketplace" in apm_data and apm_data["marketplace"] is not None: | ||
| if not force: |
There was a problem hiding this comment.
migrate_marketplace_yml() assumes rt.load(apm_text) returns a mapping; if apm.yml is empty or not a YAML mapping, "marketplace" in apm_data will raise a TypeError. Add an explicit type check and raise MarketplaceYmlError with a clear message before proceeding.
| if not isinstance(data, dict): | ||
| return False | ||
| if "marketplace" not in data or data["marketplace"] is None: | ||
| return False | ||
| return True |
There was a problem hiding this comment.
_is_apm_yml_with_marketplace() returns True even when the marketplace value is not a mapping. That can make _get_marketplace_container() return a non-dict and then container.get(...) will raise AttributeError. Tighten the check to require isinstance(data["marketplace"], dict) (or a ruamel CommentedMap).
| existing_text = apm_path.read_text(encoding="utf-8") | ||
| data = rt.load(existing_text) | ||
| except Exception as exc: # noqa: BLE001 -- guard malformed apm.yml | ||
| logger.error(f"Failed to parse apm.yml: {exc}", symbol="error") | ||
| sys.exit(1) |
There was a problem hiding this comment.
If apm.yml is empty or parses to a non-mapping, rt.load() may return None/other types and data["marketplace"] = ... will crash. Consider initializing data to an empty mapping when load returns None, and erroring out when it returns a non-mapping.
|
|
||
| Covers inheritance of name/description/version from the apm.yml top | ||
| level, override semantics inside the marketplace block, and rejection | ||
| of unknown keys at both levels. |
There was a problem hiding this comment.
The module docstring claims "rejection of unknown keys at both levels", but load_marketplace_from_apm_yml only enforces strict keys inside the marketplace: block (top-level apm.yml keys are intentionally not strict). Please update the docstring to match the actual behavior.
| of unknown keys at both levels. | |
| of unknown keys within the marketplace block. |
- migration.py: wrap ruamel apm.yml load; raise typed
MarketplaceYmlError("apm.yml is malformed: ...") instead of
leaking ruamel.yaml.YAMLError to the caller. Mirrors the existing
legacy marketplace.yml error path.
- init.py: when 'apm init --marketplace' is invoked but the
marketplace_authoring experimental flag is disabled, append the
block (option b -- lower friction, harmless if unused) and emit a
CommandLogger.warning() pointing at the flag name and enablement
command.
- yml_editor.py: add 'data: object' type hint to
_is_apm_yml_with_marketplace() to satisfy the project-wide type-hint
requirement.
- CHANGELOG.md: condense Unreleased marketplace entries to one line
per entry per Keep a Changelog convention; strip nested bullets
and prose.
Tests:
- test_migrate_with_malformed_apm_yml_raises_typed_error
- TestInitMarketplaceFlagWarnsWhenExperimentalDisabled
::test_warns_with_experimental_flag_name
Full unit suite: 6759 passed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…loses #722) (#1042) * fix(marketplace): address PR #1038 review comments + docs refresh Twelve findings from the copilot-pull-request-reviewer pass on PR #1038. Code fixes (in src/): - Remove unused DEPRECATION_MESSAGE import in commands/marketplace.py - Remove unused LOCAL_SOURCE_RE import in marketplace/yml_editor.py - _has_marketplace_block() now raises MarketplaceYmlError on YAML parse errors and OS read errors instead of swallowing them as 'no config' -- fixes a misleading message on malformed apm.yml. - migrate_marketplace_yml() validates that apm.yml round-trips to a mapping; empty apm.yml now treated as an empty mapping (CommentedMap) so the marketplace block can still be inserted. - _is_apm_yml_with_marketplace() now requires the marketplace value itself to be a mapping; previously a non-dict value would crash _get_marketplace_container() callers on .get() access. - 'apm marketplace init' applies the same empty-vs-non-mapping guard on apm.yml round-trip; non-mapping top level is a hard error, empty file is treated as an empty mapping. - 'apm init --marketplace' no longer derives marketplace owner from the project name (which produced misleading github.com/<project> URLs); the template's acme-org placeholder is used instead. - _check_gitignore_for_marketplace_json warning text refreshed: 'Both apm.yml and the generated marketplace.json must be tracked'. - Renamed test_source_dot_traversal to test_local_source_accepted (the behavior changed at fold time). - init_template.py module docstring now describes both renderers. - test_apm_yml_marketplace_loader.py docstring corrected: strict-key enforcement is inside the marketplace block only. Regression tests (tests/unit/marketplace/test_review_fixes.py, +12): - malformed apm.yml surfaces a clear MarketplaceYmlError - migrate rejects list/scalar top level, accepts empty file - _is_apm_yml_with_marketplace rejects non-mapping marketplace values - 'apm marketplace init' rejects non-mapping apm.yml, accepts empty Docs (delivered by doc-writer agent): - Full rewrite of docs/src/content/docs/guides/marketplace-authoring.md around the apm.yml block; cites microsoft/azure-skills as the byte-for-byte build proof. Adds local-path packages section and a migration section. - One-line fix in guides/marketplaces.md (marketplace.yml -> apm.yml). - reference/cli-commands.md: rewrote init/build/outdated/check/doctor blurbs, added 'apm marketplace migrate' reference, added '--marketplace' flag to 'apm init' options/examples. - reference/manifest-schema.md: added optional 'marketplace:' to the top-level shape with a pointer to the authoring guide. - packages/apm-guide/.apm/skills/apm-usage/commands.md and package-authoring.md: refreshed authoring tables and shape; called out experimental gate and deprecation. - CHANGELOG.md: Added/Changed/Deprecated entries under [Unreleased] citing #1038. Validation: - 6757 unit tests pass (6745 prior + 12 new regression). - Real-world build proof: cloned microsoft/azure-skills, appended a marketplace: block to its apm.yml derived from the hand-authored marketplace.json, ran 'apm marketplace build', and diffed -- byte- for-byte identical (sha256 02f76bfc...). Closes review of #1038. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: recompile gh-aw workflows to v0.71.2 Picks up the AW_APM_PACKAGES JSON-array fix from gh-aw v0.71.2 (shared/apm.md realignment in github/gh-aw#29002), which caused the PR Review Panel run on PR #1042 to fail at the 'Validate downloaded bundles match matrix manifest' step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(marketplace): address remaining 4 review-bot comments on PR #1038 - migration.py: wrap ruamel apm.yml load; raise typed MarketplaceYmlError("apm.yml is malformed: ...") instead of leaking ruamel.yaml.YAMLError to the caller. Mirrors the existing legacy marketplace.yml error path. - init.py: when 'apm init --marketplace' is invoked but the marketplace_authoring experimental flag is disabled, append the block (option b -- lower friction, harmless if unused) and emit a CommandLogger.warning() pointing at the flag name and enablement command. - yml_editor.py: add 'data: object' type hint to _is_apm_yml_with_marketplace() to satisfy the project-wide type-hint requirement. - CHANGELOG.md: condense Unreleased marketplace entries to one line per entry per Keep a Changelog convention; strip nested bullets and prose. Tests: - test_migrate_with_malformed_apm_yml_raises_typed_error - TestInitMarketplaceFlagWarnsWhenExperimentalDisabled ::test_warns_with_experimental_flag_name Full unit suite: 6759 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(marketplace): teach unified 'apm pack' workflow - Rewrite marketplace authoring guide to use 'apm pack' and the apm.yml marketplace: block as the single source of truth. - Update CLI command reference: remove 'apm marketplace build' entry, refresh 'apm pack' flag table, refresh 'apm marketplace init'. - Update apm-usage skill (commands.md) to match. - Remove all references to the marketplace_authoring experimental flag. Closes part of #722. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(pack): unify apm pack to produce bundle and marketplace.json Reads apm.yml and detects which artifacts to produce based on the presence of 'dependencies:' (bundle) and 'marketplace:' (marketplace.json) blocks. A single 'apm pack' invocation now replaces the legacy 'apm marketplace build' subcommand. Changes: - New BuildOrchestrator (src/apm_cli/core/build_orchestrator.py) with pluggable ArtifactProducer protocol and BundleProducer + MarketplaceProducer implementations. - pack command gains --offline, --include-prerelease, and --marketplace-output flags. Help text documents exit codes. - 'apm marketplace build' is hard-removed: invoking it exits 2 with a one-line migration message. - 'marketplace_authoring' experimental flag deleted (GA). - 'apm marketplace init' and 'apm init --marketplace' next-step hints now point at 'apm pack'. - 'apm marketplace publish' error wording updated. - New tests: 14 orchestrator unit tests, 9 pack integration tests, and one byte-for-byte snapshot test against microsoft/azure-skills@bef1f05 (sha256 02f76bfc0e5bbf7fdf1de1dda1f84c4da6e986913b6647973c0ffe39c1d5003b). - Stale tests removed: test_marketplace_build.py, test_marketplace_gating.py, and the marketplace_authoring experimental-flag class. - CHANGELOG updated under Added / Changed / Removed. Validation: - 6706 unit + console tests pass (uv run pytest tests/unit tests/test_console.py) - 10 new integration tests pass - azure-skills snapshot proof matches byte-for-byte Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(changelog): condense apm pack entry to one line Per copilot-pull-request-reviewer comment on PR #1042: Keep a Changelog entries should be one concise line per PR. The previous entry (418 chars, multi-clause) is condensed to 165 chars matching the convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(release): cut 0.11.0 Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): tighten 0.11.0 entries to lead with user impact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… block (closes #722, implements #1036) (#1038) * Fold marketplace.yml into apm.yml as a 'marketplace:' block Marketplace authoring config now lives inside apm.yml under a top-level 'marketplace:' key instead of a separate marketplace.yml file. The compiled marketplace.json (Anthropic spec) is unchanged. Legacy marketplace.yml continues to work with a deprecation warning; if both files are present, commands hard-error. Schema layer - Rename MarketplaceYml -> MarketplaceConfig (alias kept for back-compat). - Add load_marketplace_from_apm_yml() and load_marketplace_from_legacy_yml() built on a shared _build_config() helper with a default_output param so legacy keeps 'marketplace.json' while apm.yml block defaults to '.claude-plugin/marketplace.json'. - Allow local-path package sources matching '^\./.*$' via LOCAL_SOURCE_RE. is_local entries skip the version/ref requirement and skip git resolution. - Add description, homepage, is_local on PackageEntry. - Add source_path, is_legacy, name/description/version_overridden on the config so the builder can decide what to emit at the top level of marketplace.json. Migration module (new) - ConfigSource enum and detect_config_source() raise on both-files-present. - load_marketplace_config() smart loader for commands, with deprecation warning when legacy marketplace.yml is the source. - migrate_marketplace_yml() folds legacy into the apm.yml block via ruamel.yaml round-trip, supports --dry-run (returns unified diff) and --force (overwrite existing block). - detect_inheritance_conflicts() flags name/description/version mismatches. Builder layer - Add MarketplaceBuilder.from_config() classmethod for caller-loaded configs; legacy constructor preserved. - _load_yml() is shape-aware: paths ending in apm.yml go through the apm.yml loader. - Local packages emit 'source' as a plain string and use the entry's description/homepage/version directly. Top-level description/version are omitted when not explicitly overridden in the marketplace block. - _compute_diff() handles string-shaped sources. Editor layer - yml_editor detects file shape via _is_apm_yml_with_marketplace and navigates data['marketplace']['packages'] for apm.yml or data['packages'] for legacy. _validate_after_write picks the right loader; _validate_source uses allow_current_dir=True. Commands layer - New _load_config_or_exit() helper used by build, check, outdated, publish. - 'apm marketplace init' injects a 'marketplace:' block into an existing apm.yml; otherwise scaffolds legacy marketplace.yml. - 'apm marketplace migrate' (new) with --force and --dry-run. - 'apm marketplace doctor' Check 5 surfaces 'run migrate' when legacy file is present. - marketplace_plugin._yml_path / _ensure_yml_exists detect shape and hard-error when both files exist. - 'apm init --marketplace' flag appends a marketplace block to the newly created apm.yml. Init template - render_marketplace_block(owner) returns an ASCII YAML snippet for the apm.yml block. render_marketplace_yml_template kept for legacy paths. Tests - New: tests/unit/marketplace/test_apm_yml_marketplace_loader.py, tests/unit/marketplace/test_migration_detection.py, tests/unit/marketplace/test_local_path_compose.py, tests/unit/commands/test_marketplace_migrate.py. - Updated: test_yml_schema.py (relaxed regex), test_marketplace_build.py (error wording), test_marketplace_check.py (loader patch target), test_marketplace_doctor.py (wording). - Full unit suite: 6749 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(marketplace): omit empty tags array from compiled marketplace.json Anthropic spec treats tags as optional pass-through. The azure-skills canonical .claude-plugin/marketplace.json omits tags when empty; emitting 'tags: []' caused a 1-line diff in round-trip validation. Validated: marketplace.json generated from microsoft/azure-skills' apm.yml (with marketplace: block) now matches the existing .claude-plugin/marketplace.json byte-for-byte. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(marketplace): always target apm.yml in init Per design.md §4.1, 'apm marketplace init' must scaffold apm.yml when absent and inject the 'marketplace:' block into it -- never fall back to creating standalone marketplace.yml files. The legacy fallback path created a divergent surface that defeated the fold. Rewrite the 22 init tests to assert the new behavior: apm.yml is always the target, --force overwrites the existing marketplace block, --name sets the apm.yml top-level name, --owner sets marketplace.owner.name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ux(marketplace): apply cli-logging-expert critique fixes Three findings from the cli-logging-expert review of the fold-into- apm.yml CLI surface: - BLOCKER: 'apm marketplace migrate' rejected '--yes' even though sibling commands ('remove', 'publish') accept it. Add --yes/-y as an alias for --force on migrate. - HIGH: 'apm marketplace init' in an empty repo emitted two [+] lines ('Created apm.yml' + 'Added marketplace block') for one atomic outcome. Collapse to a single line: '[+] Created apm.yml with marketplace block'. The injection-only path (existing apm.yml) keeps its original message. - HIGH: re-running 'apm marketplace init' against an apm.yml that already has a marketplace block emitted [x] (error symbol) for what is a guard, not a failure. Switch to logger.warning + [!] symbol. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…loses #722) (#1042) * fix(marketplace): address PR #1038 review comments + docs refresh Twelve findings from the copilot-pull-request-reviewer pass on PR #1038. Code fixes (in src/): - Remove unused DEPRECATION_MESSAGE import in commands/marketplace.py - Remove unused LOCAL_SOURCE_RE import in marketplace/yml_editor.py - _has_marketplace_block() now raises MarketplaceYmlError on YAML parse errors and OS read errors instead of swallowing them as 'no config' -- fixes a misleading message on malformed apm.yml. - migrate_marketplace_yml() validates that apm.yml round-trips to a mapping; empty apm.yml now treated as an empty mapping (CommentedMap) so the marketplace block can still be inserted. - _is_apm_yml_with_marketplace() now requires the marketplace value itself to be a mapping; previously a non-dict value would crash _get_marketplace_container() callers on .get() access. - 'apm marketplace init' applies the same empty-vs-non-mapping guard on apm.yml round-trip; non-mapping top level is a hard error, empty file is treated as an empty mapping. - 'apm init --marketplace' no longer derives marketplace owner from the project name (which produced misleading github.com/<project> URLs); the template's acme-org placeholder is used instead. - _check_gitignore_for_marketplace_json warning text refreshed: 'Both apm.yml and the generated marketplace.json must be tracked'. - Renamed test_source_dot_traversal to test_local_source_accepted (the behavior changed at fold time). - init_template.py module docstring now describes both renderers. - test_apm_yml_marketplace_loader.py docstring corrected: strict-key enforcement is inside the marketplace block only. Regression tests (tests/unit/marketplace/test_review_fixes.py, +12): - malformed apm.yml surfaces a clear MarketplaceYmlError - migrate rejects list/scalar top level, accepts empty file - _is_apm_yml_with_marketplace rejects non-mapping marketplace values - 'apm marketplace init' rejects non-mapping apm.yml, accepts empty Docs (delivered by doc-writer agent): - Full rewrite of docs/src/content/docs/guides/marketplace-authoring.md around the apm.yml block; cites microsoft/azure-skills as the byte-for-byte build proof. Adds local-path packages section and a migration section. - One-line fix in guides/marketplaces.md (marketplace.yml -> apm.yml). - reference/cli-commands.md: rewrote init/build/outdated/check/doctor blurbs, added 'apm marketplace migrate' reference, added '--marketplace' flag to 'apm init' options/examples. - reference/manifest-schema.md: added optional 'marketplace:' to the top-level shape with a pointer to the authoring guide. - packages/apm-guide/.apm/skills/apm-usage/commands.md and package-authoring.md: refreshed authoring tables and shape; called out experimental gate and deprecation. - CHANGELOG.md: Added/Changed/Deprecated entries under [Unreleased] citing #1038. Validation: - 6757 unit tests pass (6745 prior + 12 new regression). - Real-world build proof: cloned microsoft/azure-skills, appended a marketplace: block to its apm.yml derived from the hand-authored marketplace.json, ran 'apm marketplace build', and diffed -- byte- for-byte identical (sha256 02f76bfc...). Closes review of #1038. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: recompile gh-aw workflows to v0.71.2 Picks up the AW_APM_PACKAGES JSON-array fix from gh-aw v0.71.2 (shared/apm.md realignment in github/gh-aw#29002), which caused the PR Review Panel run on PR #1042 to fail at the 'Validate downloaded bundles match matrix manifest' step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(marketplace): address remaining 4 review-bot comments on PR #1038 - migration.py: wrap ruamel apm.yml load; raise typed MarketplaceYmlError("apm.yml is malformed: ...") instead of leaking ruamel.yaml.YAMLError to the caller. Mirrors the existing legacy marketplace.yml error path. - init.py: when 'apm init --marketplace' is invoked but the marketplace_authoring experimental flag is disabled, append the block (option b -- lower friction, harmless if unused) and emit a CommandLogger.warning() pointing at the flag name and enablement command. - yml_editor.py: add 'data: object' type hint to _is_apm_yml_with_marketplace() to satisfy the project-wide type-hint requirement. - CHANGELOG.md: condense Unreleased marketplace entries to one line per entry per Keep a Changelog convention; strip nested bullets and prose. Tests: - test_migrate_with_malformed_apm_yml_raises_typed_error - TestInitMarketplaceFlagWarnsWhenExperimentalDisabled ::test_warns_with_experimental_flag_name Full unit suite: 6759 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(marketplace): teach unified 'apm pack' workflow - Rewrite marketplace authoring guide to use 'apm pack' and the apm.yml marketplace: block as the single source of truth. - Update CLI command reference: remove 'apm marketplace build' entry, refresh 'apm pack' flag table, refresh 'apm marketplace init'. - Update apm-usage skill (commands.md) to match. - Remove all references to the marketplace_authoring experimental flag. Closes part of #722. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(pack): unify apm pack to produce bundle and marketplace.json Reads apm.yml and detects which artifacts to produce based on the presence of 'dependencies:' (bundle) and 'marketplace:' (marketplace.json) blocks. A single 'apm pack' invocation now replaces the legacy 'apm marketplace build' subcommand. Changes: - New BuildOrchestrator (src/apm_cli/core/build_orchestrator.py) with pluggable ArtifactProducer protocol and BundleProducer + MarketplaceProducer implementations. - pack command gains --offline, --include-prerelease, and --marketplace-output flags. Help text documents exit codes. - 'apm marketplace build' is hard-removed: invoking it exits 2 with a one-line migration message. - 'marketplace_authoring' experimental flag deleted (GA). - 'apm marketplace init' and 'apm init --marketplace' next-step hints now point at 'apm pack'. - 'apm marketplace publish' error wording updated. - New tests: 14 orchestrator unit tests, 9 pack integration tests, and one byte-for-byte snapshot test against microsoft/azure-skills@bef1f05 (sha256 02f76bfc0e5bbf7fdf1de1dda1f84c4da6e986913b6647973c0ffe39c1d5003b). - Stale tests removed: test_marketplace_build.py, test_marketplace_gating.py, and the marketplace_authoring experimental-flag class. - CHANGELOG updated under Added / Changed / Removed. Validation: - 6706 unit + console tests pass (uv run pytest tests/unit tests/test_console.py) - 10 new integration tests pass - azure-skills snapshot proof matches byte-for-byte Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(changelog): condense apm pack entry to one line Per copilot-pull-request-reviewer comment on PR #1042: Keep a Changelog entries should be one concise line per PR. The previous entry (418 chars, multi-clause) is condensed to 165 chars matching the convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(release): cut 0.11.0 Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): tighten 0.11.0 entries to lead with user impact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Folds publisher-side
marketplace.ymlinto the existing consumer manifestapm.ymlunder a singlemarketplace:block, eliminating the dual-file authoring surface flagged in #722.Closes #722. Implements #1036.
Why
apm.ymlwas the consumer surface (declares dependencies).marketplace.ymlwas the publisher surface (declares packages this repo offers). Two YAML files in the same repo with overlapping vocabulary (name,version,description,owner) created discoverability friction and template confusion.A supply-chain analysis of 6 same-repo dual-role ecosystems (npm, Cargo, PyPI, Composer, Maven, Nix) shows all 6 use a single sectioned manifest (e.g. npm's
publishConfig). The two-file pattern only appears when the publisher lives in a different repo (Homebrew taps, Helm). APM's prior shape was novel and lost the consolidation benefit.What
marketplace:block inapm.ymlcarrying owner, build defaults, and packages.apm marketplace init|build|checknow read/writeapm.ymlexclusively.apm marketplace migrateone-shot consolidates legacymarketplace.ymlintoapm.ymland removes the file.marketplace.ymlstill loads with a deprecation warning (one release).migrate.source: ./.github/plugins/foo) are now first-class — they were rejected by the previousSOURCE_RE.marketplace.jsonfiles like the one inmicrosoft/azure-skills.How
apm_cli/marketplace/yml_schema.py— newload_marketplace_from_apm_ymlloader.apm_cli/commands/marketplace.py— init scaffoldsapm.ymlwhen absent and always injects the block; never falls back tomarketplace.yml.apm_cli/marketplace/builder.py— conditionaltagsemission, local-path source handling.tests/unit/marketplace/test_apm_yml_marketplace_loader.pytests/unit/marketplace/test_local_path_compose.pytests/unit/marketplace/test_migration_detection.pytests/unit/commands/test_marketplace_init.py(rewritten for new behavior)Validation
Cloned
microsoft/azure-skills, appended amarketplace:block to its existingapm.ymlper the new schema, ranapm marketplace build, and diffed against the repo's hand-authored.claude-plugin/marketplace.json— byte-for-byte match.CLI UX review
Captured the surface across 7 scenarios (init in empty repo / existing apm.yml / re-init guard /
--force/ legacy deprecation warning / both-files hard error / migrate). The cli-logging-expert flagged 3 issues, all addressed in commit03f06cd7:apm marketplace migrate --yescrashed because the flag was named--force, while sibling commands (remove,publish) accept--yes. Fixed:--yes/-yis now an alias for--forceonmigrate.[+]lines for one atomic outcome. Collapsed to[+] Created apm.yml with 'marketplace:' block.[x](error) for what is a guard. Switched to[!](warning).Trade-offs
marketplace.ymlstill works with a deprecation warning. Slated for removal in the next minor.marketplace.jsonremains the Anthropic canonical format.How to test