Skip to content

feat(pack): add --check-versions and --check-clean release gates#1365

Merged
danielmeppiel merged 5 commits into
mainfrom
danielmeppiel/wave-4-release-gates
May 18, 2026
Merged

feat(pack): add --check-versions and --check-clean release gates#1365
danielmeppiel merged 5 commits into
mainfrom
danielmeppiel/wave-4-release-gates

Conversation

@danielmeppiel
Copy link
Copy Markdown
Collaborator

TL;DR

Two additive apm pack flags + one schema field + one apm marketplace doctor row give monorepo marketplace producers the release-time gates they currently lack. --check-versions validates per-package versions against a declared marketplace.versioning.strategy (lockstep / tag_pattern / per_package). --check-clean regenerates marketplace.json into a temp dir and diffs it against the committed copy to catch stale artifacts. Both are opt-in, both compose, both report through the same [+]/[!]/[x]/[i] console + structured --json envelope.

Problem (WHY)

Today, a producer of a monorepo marketplace (e.g. zava-agent-configs) has no way for CI to refuse a release when:

  • Per-package apm.yml versions drift (auth at 0.9.0, billing at 1.0.0) and the producer intended a lockstep cut.
  • The committed marketplace.json is stale -- somebody edited a package's description or added a plugin and forgot to re-run apm pack.
  • An aggregator scaffold has no opinion on which versioning model the producer chose; both lockstep (Anthropic plugins convention) and tag_pattern (semantic-release convention) are legitimate.

PROSE captures the gap: "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action." -- until today, the deterministic check for "is this marketplace shippable?" lived in producers' heads. Discussion #1322 and the producer-experience epic #1348 both surface this gap explicitly.

Approach (WHAT)

Additive -- no existing command, output, or exit code changes when neither flag is passed.

Surface Change
apm pack --check-versions New flag. Validates per-package versions against marketplace.versioning.strategy. Exit 3 on misalignment.
apm pack --check-clean New flag. Regenerates marketplace.json into a temp dir, diffs against committed copy. Exit 4 on drift.
apm.yml schema New optional block marketplace.versioning: { strategy: lockstep | tag_pattern | per_package }. Default lockstep. Strict key set.
apm marketplace doctor New row "version alignment" surfacing the same logic as --check-versions, but informational only (no exit-code change).
--json envelope Two new keys version_alignment and drift, always present (set to null when the corresponding flag was not passed).

Both flags compose with --dry-run and --json. When both gates fail, exit 3 wins over exit 4 (version drift is the more diagnostic signal -- fix versions first, regenerate, then re-check clean).

Implementation (HOW)

  • src/apm_cli/marketplace/version_check.py (NEW, 239 lines) -- pure-function module. check_versions(config, packages) -> VersionCheckResult with per-package (path, version, ok, reason) rows. Three strategy implementations, each independently testable. No I/O, no console.
  • src/apm_cli/marketplace/drift_check.py (NEW, 257 lines) -- wraps BuildOptions(dry_run=True) to regenerate marketplace.json into a tempdir, then byte-compares against the committed copy per configured output format. Never writes to the working tree.
  • src/apm_cli/marketplace/yml_schema.py (+47) -- new MarketplaceVersioning Pydantic model + versioning: MarketplaceVersioning = Field(default_factory=...) on MarketplaceConfig. Extra keys rejected (model_config = ConfigDict(extra="forbid")).
  • src/apm_cli/commands/pack.py (+152) -- --check-versions / --check-clean click flags + exit-code resolution (3 wins over 4) + --json envelope extension. Gates skip with [i] info row when apm.yml has no marketplace: block.
  • src/apm_cli/commands/marketplace/doctor.py (+31) -- Check 8 row "version alignment", guarded by hasattr(yml_obj, "versioning") for legacy DTOs (no schema migration required).
  • 6 test files (NEW or extended), 65 tests total. Pack CLI flag tests and pack tests gained an autouse _reset_console fixture to neutralize the global set_console_stderr state under --json.

Permalinks: version_check.py - drift_check.py - pack.py - yml_schema.py - doctor.py.

Diagrams

Algorithm sequence for apm pack --check-versions --check-clean -- shows that both gates run after the normal pack pipeline so producers see what they would have shipped before exit-code resolution refuses it.

sequenceDiagram
    autonumber
    participant U as Producer / CI
    participant P as apm pack
    participant V as version_check
    participant D as drift_check
    participant FS as Working tree

    U->>P: apm pack --check-versions --check-clean
    P->>FS: load apm.yml + per-package apm.yml
    P->>P: run normal pack pipeline (dry-run honored)
    P->>V: check_versions(config, packages)
    V-->>P: VersionCheckResult(ok, expected, rows[])
    P->>D: check_clean(config, outputs)
    Note over D,FS: regenerates into tempdir<br/>never writes to working tree
    D-->>P: DriftCheckResult(ok, per-format diffs)
    P->>P: resolve exit code (3 > 4 > 0)
    P-->>U: console output + JSON envelope + exit code
Loading

Exit-code resolution -- captures the precedence rule explicitly so reviewers can verify it without reading source.

flowchart LR
    Start([gates evaluated]) --> Q1{version misalign?}
    Q1 -->|yes| E3["exit 3<br/>(version wins)"]
    Q1 -->|no| Q2{marketplace.json drift?}
    Q2 -->|yes| E4[exit 4]
    Q2 -->|no| Q3{pack errors?}
    Q3 -->|yes| E1[exit 1 or 2]
    Q3 -->|no| E0[exit 0]
Loading

Trade-offs

  • Default strategy is lockstep, not per_package. Lockstep matches the Anthropic marketplace convention and what zava-agent-configs ships today; per_package would have been a stricter "do nothing surprising" default but would mute the check for the majority of monorepo producers who actually want lockstep.
  • Exit 3 > exit 4 when both fail. Version drift is the more actionable signal -- fixing versions usually requires regenerating marketplace.json anyway, so reporting drift first would be a misleading lead. Producers see both rows in console output regardless; only the exit code is single-valued.
  • --check-clean re-runs the full pack pipeline. Slower than a hash compare against a manifest, but a manifest would need a new file to maintain and marketplace.json regeneration is already idempotent and fast (sub-second on zava-scale repos).
  • marketplace.versioning is opt-in additive, not required. Existing apm.yml files keep working; marketplace.versioning defaults to lockstep only when --check-versions is invoked, so silent producers see no behavior change.
  • apm-action pass-through deferred to Wave 5. This PR ships the CLI primitives; the microsoft/apm-action mode: release umbrella that wires --check-versions / --check-clean into CI lands in a separate PR per the plan's dependency graph.

Benefits

  1. CI can now refuse a misaligned release. A two-line workflow step (apm pack --check-versions --check-clean) catches version drift and stale marketplace.json before tag push.
  2. Schema documents intent. marketplace.versioning.strategy makes the producer's release model explicit and reviewable in the repo, not tribal knowledge.
  3. marketplace doctor becomes a single-screen release-readiness dashboard. Format coverage + duplicate names + version alignment all in one table.
  4. Structured output for orchestrators. --json envelope adds version_alignment and drift keys, so the upcoming apm-action mode: release (Wave 5) can render a GitHub Step Summary without re-parsing console text.
  5. Zero existing-flow change. All 8,567 pre-existing unit tests pass with no edits; producers who don't opt in see identical behavior.

Validation

Lint (canonical contract per .apm/instructions/linting.instructions.md):

$ uv run --extra dev ruff check src/ tests/ && uv run --extra dev ruff format --check src/ tests/
All checks passed!
177 files already formatted

Wave-4 test slice (65 tests covering the new modules + extended CLI / doctor / schema tests):

$ uv run --extra dev pytest \
    tests/unit/marketplace/test_version_check.py \
    tests/unit/marketplace/test_drift_check.py \
    tests/unit/marketplace/test_yml_schema.py \
    tests/unit/commands/test_pack_cli_flags.py \
    tests/unit/commands/test_pack.py \
    tests/unit/commands/test_marketplace_doctor.py -q
================== 177 passed in 1.84s ===================

Full unit suite -- no regressions:

$ uv run --extra dev pytest tests/unit/ -q --tb=line
================== 8567 passed in 49.21s ==================

Scenario Evidence

Each user-promise scenario this PR touches, the test that proves it, and the APM principle the scenario serves.

User promise scenario Test APM principle
--check-versions refuses lockstep mismatch with exit 3 tests/unit/commands/test_pack_cli_flags.py::test_check_versions_lockstep_mismatch_exits_3 Determinism: gate produces a single, reproducible exit code
--check-versions passes when all packages aligned tests/unit/marketplace/test_version_check.py::test_lockstep_all_aligned_ok Composability: gate is silent when there is nothing to report
tag_pattern strategy ignores stale top-level version: tests/unit/marketplace/test_version_check.py::test_tag_pattern_ignores_top_level_version Progressive Disclosure: producers opt into strict checks per strategy
per_package strategy never errors on version skew tests/unit/marketplace/test_version_check.py::test_per_package_never_errors Producer autonomy: schema documents intent, doesn't impose policy
--check-clean refuses drift with exit 4 tests/unit/marketplace/test_drift_check.py::test_drift_detected_when_marketplace_json_stale Determinism: committed artifact must equal regenerated artifact
--check-clean never writes to working tree tests/unit/marketplace/test_drift_check.py::test_check_clean_never_writes_to_working_tree Safety: read-only gates are read-only
Exit 3 wins over exit 4 when both fail tests/unit/commands/test_pack_cli_flags.py::test_check_versions_wins_over_check_clean_exit_code Determinism: precedence is single-valued
--json envelope carries both new keys, always present tests/unit/commands/test_pack_cli_flags.py::test_json_envelope_contains_version_alignment_and_drift_keys Composability: structured output is contract, not optional
Gates skip cleanly when apm.yml has no marketplace block tests/unit/commands/test_pack_cli_flags.py::test_check_flags_skip_on_non_marketplace_apm_yml Progressive Disclosure: features apply only where relevant
marketplace doctor row surfaces version alignment tests/unit/commands/test_marketplace_doctor.py::test_doctor_renders_version_alignment_row Observability: same logic, two surfaces (gate + diagnostic)
Schema rejects unknown keys under marketplace.versioning tests/unit/marketplace/test_yml_schema.py::test_versioning_extra_key_rejected Strictness: schema is the contract

End-to-end smoke (real fixture)

Console output for all four scenarios on a zava-style monorepo fixture
===== S1: --check-versions on lockstep mismatch (auth=0.9.0, billing=1.0.0) =====
[x] Version alignment failed [strategy=lockstep, expected=1.0.0]
[i]     packages/auth  0.9.0  [drift:expected=1.0.0]
[i]     packages/billing  1.0.0  [matches]
[i] [dry-run] Would write marketplace.json [claude] (2 package(s)) ->
    /private/tmp/wave4-demo/.claude-plugin/marketplace.json
EXIT: 3

===== S2: --check-versions on aligned tree =====
[*] Version alignment OK [strategy=lockstep, expected=1.0.0]
[i]     packages/auth  1.0.0  [matches]
[i]     packages/billing  1.0.0  [matches]
EXIT: 0

===== S3: --check-clean on freshly-regenerated tree =====
[*] Marketplace working tree clean [outputs=claude]
[i]     .claude-plugin/marketplace.json  [unchanged]
EXIT: 0

===== S4: --check-versions --check-clean --json (both gates) =====
{
  "ok": false,
  "version_alignment": {
    "strategy": "lockstep",
    "expected": "1.0.0",
    "ok": false,
    "packages": [
      {"path": "packages/auth",    "version": "0.9.0", "ok": false, "reason": "drift:expected=1.0.0"},
      {"path": "packages/billing", "version": "1.0.0", "ok": true,  "reason": "matches"}
    ]
  },
  "drift": {
    "ok": true,
    "outputs": [
      {"format": "claude", "path": ".claude-plugin/marketplace.json", "status": "unchanged", "differences": []}
    ]
  }
}
EXIT: 3   # version (3) wins over drift (4)
===== marketplace doctor -- version-alignment row =====
| version alignment  | [i]    | strategy=lockstep, 1/2 packages misaligned: packages/auth |

How to test

  1. Check out the branch: gh pr checkout <this-pr>.
  2. Scaffold a 2-package monorepo per the validation transcript above (or git clone https://github.com/microsoft/zava-agent-configs && cd zava-agent-configs).
  3. Run apm pack --check-versions --dry-run -- should exit 0 on an aligned tree, 3 on a misaligned tree.
  4. Run apm pack --check-clean after editing a package description without re-running apm pack -- should exit 4 and print the drift in .claude-plugin/marketplace.json.
  5. Run apm marketplace doctor -- confirm the new "version alignment" row appears as the last row.

Note

The apm-action pass-through that wires these flags into a one-line CI step (mode: release) lands in Wave 5 of the producer-experience plan; this PR ships only the CLI primitives. Producers can still use the flags today by adding a step to their workflow that calls apm pack --check-versions --check-clean.

Part of #1348.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Daniel Meppiel and others added 2 commits May 18, 2026 13:59
Adds two opt-in release gates to apm pack for producer-experience Wave 4:

- --check-versions (exit 3): validates package version alignment per
  marketplace.versioning.strategy (lockstep | tag_pattern | per_package).
- --check-clean (exit 4): rebuilds marketplace.json in-memory and diffs
  against the on-disk copy, failing if drift is detected.

When both flags fail, exit 3 wins (version misalignment is the more
actionable signal). The --json envelope always includes
'version_alignment' and 'drift' keys (null when the flag was not
requested) so CI can branch on payload shape.

Also adds:
- marketplace.versioning schema (yml_schema.py): strategy + tag_pattern
  with backwards-compatible defaults (lockstep).
- apm marketplace doctor: informational version-alignment row that
  surfaces strategy + per-package status without failing exit code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 18, 2026 12:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds two opt-in release-time gates to apm pack for monorepo marketplace producers: --check-versions (per-package version alignment against marketplace.versioning.strategy) and --check-clean (regenerate-and-diff against the committed marketplace.json). Introduces a new optional marketplace.versioning schema block, an informational row in apm marketplace doctor, and extends the --json envelope with version_alignment and drift keys. Exit code 3 (version misalignment) takes precedence over exit code 4 (drift). Fully additive: behaviour is unchanged when neither flag is passed.

Changes:

  • New pure-helper modules marketplace/version_check.py and marketplace/drift_check.py (lockstep / tag_pattern / per_package strategies and per-output JSON diff).
  • pack.py wires the two new flags, gate execution after the normal pipeline, and exit-code resolution; --json envelope always carries the two new keys (null when not requested).
  • Schema gains MarketplaceVersioning (strict key set, strategy enum); doctor adds an informational "version alignment" Check 8 guarded by hasattr(yml_obj, "versioning").
Show a summary per file
File Description
src/apm_cli/commands/pack.py Adds --check-versions / --check-clean flags, gate execution, exit-code resolution (3 > 4), JSON envelope extension.
src/apm_cli/commands/marketplace/doctor.py New informational "version alignment" row using the same version_check helper.
src/apm_cli/marketplace/version_check.py New pure module implementing three alignment strategies plus JSON / human-readable reporting.
src/apm_cli/marketplace/drift_check.py New pure module that regenerates outputs via a dry-run builder and diffs against on-disk JSON.
src/apm_cli/marketplace/yml_schema.py Adds MarketplaceVersioning dataclass, parser, strategy validation, and versioning field on MarketplaceConfig.
CHANGELOG.md Documents the four user-visible additions under Unreleased.
tests/unit/marketplace/test_yml_schema.py New TestVersioningBlock suite covering defaults, valid strategies, and rejection paths.
tests/unit/marketplace/test_version_check.py New 343-line test module covering all three strategies, JSON shape, error helper.
tests/unit/marketplace/test_drift_check.py New 248-line module covering clean / drift / missing / mixed-outputs cases and the key-diff helper.
tests/unit/commands/test_pack_cli_flags.py Adds help-text / flag-skip / exit-code / JSON-envelope tests; introduces autouse console-reset fixture.
tests/unit/commands/test_pack.py Adds end-to-end gate integration tests with the same console-reset fixture.
tests/unit/commands/test_marketplace_doctor.py New TestDoctorVersionAlignment suite for the Check 8 row.

Copilot's findings

  • Files reviewed: 12/12 changed files
  • Comments generated: 3

Comment on lines +125 to +145
@click.option(
"--check-versions",
is_flag=True,
default=False,
help=(
"Release gate: verify per-package versions agree with the configured "
"marketplace.versioning.strategy (lockstep | tag_pattern | per_package). "
"Exits 3 on misalignment. Composes with --check-clean and --dry-run."
),
)
@click.option(
"--check-clean",
is_flag=True,
default=False,
help=(
"Release gate: regenerate every configured marketplace output to a "
"temp path and diff against the on-disk file. Exits 4 if the working "
"tree is dirty (out-of-date marketplace.json). The gate itself "
"never writes to disk."
),
)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2438458: appended --check-versions and --check-clean (with exit codes 3 and 4) to the pack row in commands.md, and documented the optional marketplace.versioning.strategy field (with the three accepted values) in package-authoring.md.

Comment on lines +111 to +120
try:
raw = yaml.safe_load(pkg_yml.read_text(encoding="utf-8"))
except yaml.YAMLError:
return None, "missing_version"
if not isinstance(raw, dict):
return None, "missing_version"
version = raw.get("version")
if not isinstance(version, str) or not version.strip():
return None, "missing_version"
return version.strip(), "ok"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2438458: _read_local_version now returns a distinct invalid_yaml status for yaml.YAMLError (and for non-dict YAML, which is also a malformed structure), with its own error_messages branch. Test added: test_invalid_yaml_distinct_from_missing_version.

Comment on lines +185 to +208
if tag in rendered:
other = rendered[tag]
rows.append(
PackageVersionRow(
path=rel,
version=version,
ok=False,
reason=f"duplicate_tag:other={other}",
rendered_tag=tag,
)
)
# Also flip the earlier-matched row to drift since both collide.
for i, prev in enumerate(rows[:-1]):
if prev.path == other and prev.ok:
rows[i] = PackageVersionRow(
path=prev.path,
version=prev.version,
ok=False,
reason=f"duplicate_tag:other={rel}",
rendered_tag=prev.rendered_tag,
)
break
else:
rendered[tag] = rel
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2438458: the duplicate-tag branch now refreshes rendered[tag] = rel after recording the collision, so the third+ colliding package blames the most recent sibling instead of the original first author. Test added: test_three_way_collision_blames_nearest_sibling.

Daniel Meppiel and others added 2 commits May 18, 2026 14:14
Three findings from Copilot review on PR #1365:

1. Doc drift in apm-guide skill resources:
   - commands.md pack row now lists --check-versions and --check-clean,
     including exit-code semantics (3 and 4) so the agent can answer
     gate-failure questions without re-reading source.
   - package-authoring.md schema example now shows the optional
     marketplace.versioning block, and Schema rules document the three
     accepted strategy values (lockstep | tag_pattern | per_package).

2. _read_local_version conflated YAML parse failures with a missing
   version: key, sending users with a malformed YAML to the wrong
   troubleshooting path. Introduce a distinct "invalid_yaml" status
   (also covers non-dict YAML, which is malformed structure) with its
   own error_messages branch.

3. The duplicate-tag branch in check_version_alignment never refreshed
   the rendered[tag] -> path mapping after a collision, so a third
   colliding package would blame the first author instead of the most
   recent. Append rendered[tag] = rel after recording the conflict so
   subsequent collisions point at the nearest sibling.

Tests:
- test_invalid_yaml_distinct_from_missing_version asserts the new
  status surfaces with its own message.
- test_three_way_collision_blames_nearest_sibling asserts C blames B
  (not A) when three packages render the same tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel merged commit fa96c42 into main May 18, 2026
9 checks passed
@danielmeppiel danielmeppiel deleted the danielmeppiel/wave-4-release-gates branch May 18, 2026 14:34
danielmeppiel added a commit that referenced this pull request May 18, 2026
* chore: cut 0.14.0

Renames the [Unreleased] block in CHANGELOG.md to [0.14.0] - 2026-05-18
and bumps the package version from 0.13.0 to 0.14.0 in pyproject.toml
(and uv.lock by regeneration).

0.14.0 ships the producer-experience epic (#1348) on the CLI side --
notably:

- apm pack --check-versions / --check-clean (#1365), the release gates
  consumed by apm-action mode: release.
- apm plugin init (#1370), the noun-verb successor to apm init --plugin.
- apm pack multi-format outputs (--marketplace, --marketplace-path,
  --json, marketplace.outputs map form) (#1317).
- New producer docs corpus (repo-shapes / releasing-from-any-ci /
  versioning-strategies) (#1370).
- Breaking: MCP registry client adopts the official v0.1 spec; self-
  hosted registries must serve /v0.1/ paths (#1337).

Plus the deprecations and fixes already listed in the moved block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(changelog): tighten v0.14.0 entries; add post-cut PRs

- One concise line per PR answering 'so what?' for end users
- Add 5 missing entries: #1376 (perf resolver), #1373 (shared/apm.md
  matrix secret-stripping), #1246 (install.ps1 GHES env vars), #1255
  (warn missing apm.yml), #1248 (extends:org unmanaged_files)
- Drop internal/CI/test-infra entries (#1270, #1271, #1272, #1274,
  #1276, #1291, #1360 refactor)
- Consolidate three #605 lines and four #1317 lines into one entry
  per PR where appropriate
- Promote MCP Registry v0.1 to a dedicated Breaking section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(changelog): add #1377 Bitbucket DC tilde fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Daniel Meppiel <copilot-rework@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
* feat(pack): add --check-versions and --check-clean release gates

Adds two opt-in release gates to apm pack for producer-experience Wave 4:

- --check-versions (exit 3): validates package version alignment per
  marketplace.versioning.strategy (lockstep | tag_pattern | per_package).
- --check-clean (exit 4): rebuilds marketplace.json in-memory and diffs
  against the on-disk copy, failing if drift is detected.

When both flags fail, exit 3 wins (version misalignment is the more
actionable signal). The --json envelope always includes
'version_alignment' and 'drift' keys (null when the flag was not
requested) so CI can branch on payload shape.

Also adds:
- marketplace.versioning schema (yml_schema.py): strategy + tag_pattern
  with backwards-compatible defaults (lockstep).
- apm marketplace doctor: informational version-alignment row that
  surfaces strategy + per-package status without failing exit code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(changelog): add Wave 4 entries for --check-versions / --check-clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(drift_check): use portable_relpath() per repo lint contract

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(pack): address PR #1365 reviewer feedback

Three findings from Copilot review on PR #1365:

1. Doc drift in apm-guide skill resources:
   - commands.md pack row now lists --check-versions and --check-clean,
     including exit-code semantics (3 and 4) so the agent can answer
     gate-failure questions without re-reading source.
   - package-authoring.md schema example now shows the optional
     marketplace.versioning block, and Schema rules document the three
     accepted strategy values (lockstep | tag_pattern | per_package).

2. _read_local_version conflated YAML parse failures with a missing
   version: key, sending users with a malformed YAML to the wrong
   troubleshooting path. Introduce a distinct "invalid_yaml" status
   (also covers non-dict YAML, which is malformed structure) with its
   own error_messages branch.

3. The duplicate-tag branch in check_version_alignment never refreshed
   the rendered[tag] -> path mapping after a collision, so a third
   colliding package would blame the first author instead of the most
   recent. Append rendered[tag] = rel after recording the conflict so
   subsequent collisions point at the nearest sibling.

Tests:
- test_invalid_yaml_distinct_from_missing_version asserts the new
  status surfaces with its own message.
- test_three_way_collision_blames_nearest_sibling asserts C blames B
  (not A) when three packages render the same tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Daniel Meppiel <copilot-rework@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: cut 0.14.0

Renames the [Unreleased] block in CHANGELOG.md to [0.14.0] - 2026-05-18
and bumps the package version from 0.13.0 to 0.14.0 in pyproject.toml
(and uv.lock by regeneration).

0.14.0 ships the producer-experience epic (#1348) on the CLI side --
notably:

- apm pack --check-versions / --check-clean (#1365), the release gates
  consumed by apm-action mode: release.
- apm plugin init (#1370), the noun-verb successor to apm init --plugin.
- apm pack multi-format outputs (--marketplace, --marketplace-path,
  --json, marketplace.outputs map form) (#1317).
- New producer docs corpus (repo-shapes / releasing-from-any-ci /
  versioning-strategies) (#1370).
- Breaking: MCP registry client adopts the official v0.1 spec; self-
  hosted registries must serve /v0.1/ paths (#1337).

Plus the deprecations and fixes already listed in the moved block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(changelog): tighten v0.14.0 entries; add post-cut PRs

- One concise line per PR answering 'so what?' for end users
- Add 5 missing entries: #1376 (perf resolver), #1373 (shared/apm.md
  matrix secret-stripping), #1246 (install.ps1 GHES env vars), #1255
  (warn missing apm.yml), #1248 (extends:org unmanaged_files)
- Drop internal/CI/test-infra entries (#1270, #1271, #1272, #1274,
  #1276, #1291, #1360 refactor)
- Consolidate three #605 lines and four #1317 lines into one entry
  per PR where appropriate
- Promote MCP Registry v0.1 to a dedicated Breaking section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(changelog): add #1377 Bitbucket DC tilde fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Daniel Meppiel <copilot-rework@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

Development

Successfully merging this pull request may close these issues.

2 participants