Skip to content

feat(marketplace): fold marketplace.yml into apm.yml's 'marketplace:' block (closes #722, implements #1036)#1038

Merged
danielmeppiel merged 4 commits into
mainfrom
fold-marketplace-into-apm
Apr 29, 2026
Merged

feat(marketplace): fold marketplace.yml into apm.yml's 'marketplace:' block (closes #722, implements #1036)#1038
danielmeppiel merged 4 commits into
mainfrom
fold-marketplace-into-apm

Conversation

@danielmeppiel

Copy link
Copy Markdown
Collaborator

Summary

Folds publisher-side marketplace.yml into the existing consumer manifest apm.yml under a single marketplace: block, eliminating the dual-file authoring surface flagged in #722.

Closes #722. Implements #1036.

Why

apm.yml was the consumer surface (declares dependencies). marketplace.yml was 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

  • New marketplace: block in apm.yml carrying owner, build defaults, and packages.
  • apm marketplace init|build|check now read/write apm.yml exclusively.
  • apm marketplace migrate one-shot consolidates legacy marketplace.yml into apm.yml and removes the file.
  • Legacy marketplace.yml still loads with a deprecation warning (one release).
  • Both files together = hard error pointing at migrate.
  • Local-path package sources (source: ./.github/plugins/foo) are now first-class — they were rejected by the previous SOURCE_RE.
  • Tags emission is conditional (omitted when empty) to match the Anthropic canonical schema produced by hand-authored marketplace.json files like the one in microsoft/azure-skills.

How

  • apm_cli/marketplace/yml_schema.py — new load_marketplace_from_apm_yml loader.
  • apm_cli/commands/marketplace.py — init scaffolds apm.yml when absent and always injects the block; never falls back to marketplace.yml.
  • apm_cli/marketplace/builder.py — conditional tags emission, local-path source handling.
  • New tests:
    • tests/unit/marketplace/test_apm_yml_marketplace_loader.py
    • tests/unit/marketplace/test_local_path_compose.py
    • tests/unit/marketplace/test_migration_detection.py
    • tests/unit/commands/test_marketplace_init.py (rewritten for new behavior)

Validation

Cloned microsoft/azure-skills, appended a marketplace: block to its existing apm.yml per the new schema, ran apm marketplace build, and diffed against the repo's hand-authored .claude-plugin/marketplace.json — byte-for-byte match.

6745 passed, 1 warning, 27 subtests passed in 44.81s

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 commit 03f06cd7:

  1. BLOCKERapm marketplace migrate --yes crashed because the flag was named --force, while sibling commands (remove, publish) accept --yes. Fixed: --yes/-y is now an alias for --force on migrate.
  2. HIGH — Init in an empty repo emitted two [+] lines for one atomic outcome. Collapsed to [+] Created apm.yml with 'marketplace:' block.
  3. HIGH — Re-init against an existing block printed [x] (error) for what is a guard. Switched to [!] (warning).

Trade-offs

  • One release of dual-load support. marketplace.yml still works with a deprecation warning. Slated for removal in the next minor.
  • Schema is unchanged. This PR only touches the authoring INPUT surface. The generated marketplace.json remains the Anthropic canonical format.
  • Single PR, ~800 LOC delta. Considered splitting (loader / commands / migrate) but the change is internally cohesive and easier to review as one unit.

How to test

# 1. Greenfield init
mkdir /tmp/mp && cd /tmp/mp
apm experimental enable marketplace-authoring
apm marketplace init --owner my-org
cat apm.yml  # marketplace: block present

# 2. Migration of legacy file
mkdir /tmp/mig && cd /tmp/mig
echo -e "name: app\nversion: 1.0.0\ndescription: x" > apm.yml
cat > marketplace.yml << 'YAML'
name: mp
version: 0.1.0
description: x
owner:
  name: acme
packages: []
YAML
apm marketplace migrate --yes
ls marketplace.yml  # gone
cat apm.yml  # marketplace: block populated

# 3. Real-repo build
git clone https://github.com/microsoft/azure-skills /tmp/az
cd /tmp/az
# (apm.yml already has the marketplace: block in this PR's validation)
apm marketplace build
diff <(jq -S . .claude-plugin/marketplace.json) <(jq -S . marketplace.json.generated)

danielmeppiel and others added 4 commits April 29, 2026 15:00
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>
Copilot AI review requested due to automatic review settings April 29, 2026 13:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.yml block vs legacy marketplace.yml) plus a new apm marketplace migrate command.
  • 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):

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
def test_source_dot_traversal(self, tmp_path: Path):
def test_local_source_accepted(self, tmp_path: Path):

Copilot uses AI. Check for mistakes.
Comment on lines +315 to +317
if not no_gitignore_check:
_check_gitignore_for_marketplace_json(logger)

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
_MARKETPLACE_BLOCK_TEMPLATE = """\
# Marketplace authoring config (APM-only).
# Run 'apm marketplace build' to compile this block to .claude-plugin/marketplace.json.
#

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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().

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +31
from .yml_schema import (
LOCAL_SOURCE_RE,
SOURCE_RE,
load_marketplace_from_apm_yml,
load_marketplace_yml,
)

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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().

Copilot uses AI. Check for mistakes.
from ..marketplace.ref_resolver import RefResolver, RemoteRef
from ..marketplace.semver import SemVer, parse_semver, satisfies_range
from ..marketplace.migration import (
DEPRECATION_MESSAGE,

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

DEPRECATION_MESSAGE is imported but never used in this file; please drop the unused import to keep linting clean.

Suggested change
DEPRECATION_MESSAGE,

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +75
"""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


Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

_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.

Suggested change
"""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
)

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +235
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:

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +79
if not isinstance(data, dict):
return False
if "marketplace" not in data or data["marketplace"] is None:
return False
return True

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

_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).

Copilot uses AI. Check for mistakes.
Comment on lines +278 to +282
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)

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.

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.

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
of unknown keys at both levels.
of unknown keys within the marketplace block.

Copilot uses AI. Check for mistakes.
@danielmeppiel danielmeppiel linked an issue Apr 29, 2026 that may be closed by this pull request
8 tasks
@danielmeppiel danielmeppiel merged commit 74f3a18 into main Apr 29, 2026
19 checks passed
@danielmeppiel danielmeppiel deleted the fold-marketplace-into-apm branch April 29, 2026 14:05
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
- 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>
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
…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>
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
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>
@danielmeppiel danielmeppiel added this to the 0.11.0 milestone Apr 29, 2026
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
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>
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
* 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>
sergio-sisternes-epam pushed a commit that referenced this pull request May 19, 2026
… 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>
sergio-sisternes-epam pushed a commit that referenced this pull request May 19, 2026
…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>
sergio-sisternes-epam pushed a commit that referenced this pull request May 19, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants