From c9b0999f5f6e774cedf232d4f199e99c57ff35d8 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 08:05:23 +0200 Subject: [PATCH 1/3] chore: release v0.24.1 Bump pyproject.toml and uv.lock to 0.24.1 and move the [Unreleased] CHANGELOG block to [0.24.1] - 2026-07-10. PATCH bump: 13 user-facing fixes since v0.24.0 with no new public surface or BREAKING changes. Lint mirror green locally (ruff check + format, pylint R0801, auth-signals). Post-merge: tag v0.24.1 to trigger the release workflow. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 68 ++++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 134ae96aa..5f7a888f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,44 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.24.1] - 2026-07-10 + ### Fixed -- Sub-skill and native-skill ownership tracking now keys on the full - dependency identity (`owner/repo`) instead of the last path segment. - Two different packages that happen to share a repo/leaf name (e.g. two - orgs each publishing a `shared-skill` or `utils` repo) were previously - treated as the same owner, silently suppressing the cross-package - collision warning exactly when it mattered -- an unrelated package - could overwrite another's skill with no signal. `apm install --force` - now correctly reports the collision regardless of dependency source - (registry or git), and the lockfile no longer records both packages as - owning the same deployed file after a collision. (by @nadav-y) (#2052) -- Codex MCP installs and stale-server cleanup now preserve literal-quoted - Windows path keys such as `C:\Users\me\Documents\Playground` in `config.toml` - instead of rejecting or corrupting other projects and per-path preferences. - (closes #2075) (#2100) -- `apm audit --ci` no longer raises false `config-consistency` failures in - monorepos whose local-path sub-packages contribute MCP servers. Transitively - contributed servers now carry lockfile provenance and are exempt from the - orphan check. (#2084) +- Skill ownership tracking now keys on the full `owner/repo` identity, so + packages with the same leaf name no longer suppress collision warnings or + claim the same deployed files. (by @nadav-y, #2052) +- Repeated `apm install` runs now preserve existing LSP server and config state + before lockfile comparison, avoiding no-op rewrites caused only by + `generated_at`. (by @paul-ww, closes #2078, #2079) +- OpenAPM v0.1 now defines deterministic Antigravity glob frontmatter and + limits `AGENTS.md` deduplication to resolved instruction files, preventing + unrelated rule files from suppressing instructions. (#2087) +- Codex MCP installs and stale-server cleanup now preserve literal Windows + path keys in `config.toml` instead of rejecting or corrupting per-project + preferences. (closes #2075, #2100) +- `apm audit --ci` no longer reports transitive MCP servers from local-path + sub-packages as orphaned configs. (by @edenfunf, closes #2081, #2084) - In-repository plugins from SSH-registered GitLab and generic git - marketplaces no longer rewrite to HTTPS; generated `git:` and `path:` - dependencies keep using existing SSH keys. (#2091) -- Installing the same GitHub or package-registry dependency with different - owner/repository casing no longer creates duplicate identities, lock/cache - keys, or install paths. Publish uses the same lowercase identity, while - unknown git hosts retain case-sensitive paths. (closes #2073) (#2098) + marketplaces now preserve SSH transport in generated `git:` and `path:` + dependencies. (#2091) +- GitHub and package-registry dependency identities are now case-insensitive, + preventing duplicate lock, cache, and install paths while preserving + case-sensitive paths for unknown git hosts. (closes #2073, #2098) - `apm deps tree` no longer repeats same-repository virtual packages, keeping large monorepo dependency trees accurate and scannable. (#2093) -- `apm install host/org/repo/subpath#ref` on an unrecognised self-hosted FQDN - no longer fails with a misleading "not accessible or doesn't exist" error; - the failure reason now suggests setting `GITLAB_HOST` / `APM_GITLAB_HOSTS` - if the target is a self-hosted GitLab instance, or using an explicit - `git:` + `path:` entry in `apm.yml` otherwise. (by @rrazvd; closes #2066) (#2074) -- Dev dependencies are no longer removed by `apm prune`, reported as orphans - by `apm audit --ci`, or omitted from lockfile and MCP config checks. This - includes `devDependencies.apm` and applicable `devDependencies.mcp` entries. - (by @sergio-sisternes-epam; #2102, supersedes #2042, closes #2033) +- Claude user-scope MCP installs now honor `CLAUDE_CONFIG_DIR`, with + `~/.claude.json` retained as the fallback. (closes #2060, #2096) +- Dev dependencies now survive `apm prune`, pass `apm audit --ci`, and remain + represented in lockfile and MCP config checks. (by @sergio-sisternes-epam, + closes #2033, #2102; supersedes #2042) +- `apm deps list` now matches local transitive dependencies by their canonical + install path instead of incorrectly reporting valid packages as orphaned. + (closes #2068, #2099) +- Failed installs from unrecognized self-hosted GitLab hosts now suggest + `GITLAB_HOST`, `APM_GITLAB_HOSTS`, or an explicit `git:` plus `path:` entry. + (by @rrazvd, closes #2066, #2074) +- `apm update` now reconciles MCP and LSP servers after an accepted plan, + removing stale runtime config and recording newly declared servers. + (by @cffnpwr, closes #2077, #2085) ## [0.24.0] - 2026-07-05 diff --git a/pyproject.toml b/pyproject.toml index c71e1807d..8c0ab8f3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "apm-cli" -version = "0.24.0" +version = "0.24.1" description = "MCP configuration tool" readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 29371572d..c7aedbbdd 100644 --- a/uv.lock +++ b/uv.lock @@ -201,7 +201,7 @@ wheels = [ [[package]] name = "apm-cli" -version = "0.24.0" +version = "0.24.1" source = { editable = "." } dependencies = [ { name = "click" }, From af749d999f884a20072d0677db7b360a8feef846 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 10:41:56 +0200 Subject: [PATCH 2/3] test(install): use real project roots in MCP integration tests Use pytest temporary directories for the four default-policy MCP integration cases so subprocess policy discovery receives a valid working directory on Windows as well as POSIX. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../test_mcp_integration_extraction.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/unit/install/test_mcp_integration_extraction.py b/tests/unit/install/test_mcp_integration_extraction.py index 665a1782f..4f7eac954 100644 --- a/tests/unit/install/test_mcp_integration_extraction.py +++ b/tests/unit/install/test_mcp_integration_extraction.py @@ -72,7 +72,7 @@ class TestRunMcpIntegrationInstallBranch: """``should_install=True`` and ``mcp_deps`` non-empty.""" @patch(_PATCH_TARGET) - def test_installs_and_updates_lockfile(self, mock_mcp): + def test_installs_and_updates_lockfile(self, mock_mcp, tmp_path: Path): dep = MCPDependency(name="io.github.acme/server", transport="stdio") mock_mcp.deduplicate.side_effect = lambda x: x mock_mcp.install.return_value = 1 @@ -82,7 +82,9 @@ def test_installs_and_updates_lockfile(self, mock_mcp): "io.github.acme/server": "io.github.acme/package" } - count, apm_config = run_mcp_integration(**_base_kwargs(mcp_deps=[dep])) + count, apm_config = run_mcp_integration( + **_base_kwargs(mcp_deps=[dep], project_root=tmp_path) + ) assert count == 1 assert apm_config == {"scripts": {}} @@ -98,7 +100,7 @@ def test_installs_and_updates_lockfile(self, mock_mcp): mock_mcp.remove_stale.assert_not_called() @patch(_PATCH_TARGET) - def test_removes_stale_servers_no_longer_declared(self, mock_mcp): + def test_removes_stale_servers_no_longer_declared(self, mock_mcp, tmp_path: Path): dep = MCPDependency(name="io.github.acme/new-server", transport="stdio") mock_mcp.deduplicate.side_effect = lambda x: x mock_mcp.install.return_value = 1 @@ -111,6 +113,7 @@ def test_removes_stale_servers_no_longer_declared(self, mock_mcp): mcp_deps=[dep], old_mcp_servers={"io.github.acme/orphan-server"}, old_mcp_configs={"io.github.acme/orphan-server": {"name": "orphan"}}, + project_root=tmp_path, ) ) @@ -119,7 +122,7 @@ def test_removes_stale_servers_no_longer_declared(self, mock_mcp): assert stale_arg == {"io.github.acme/orphan-server"} @patch(_PATCH_TARGET) - def test_forwards_apm_config_targets_key_when_declared(self, mock_mcp): + def test_forwards_apm_config_targets_key_when_declared(self, mock_mcp, tmp_path: Path): """#1335: only the targets-key the user actually declared is forwarded, matching the original inline block's behaviour.""" dep = MCPDependency(name="io.github.acme/server", transport="stdio") @@ -130,14 +133,16 @@ def test_forwards_apm_config_targets_key_when_declared(self, mock_mcp): mock_mcp.get_server_provenance.return_value = {} pkg = _make_apm_package(scripts={"build": "echo hi"}, targets=["claude", "copilot"]) - _count, apm_config = run_mcp_integration(**_base_kwargs(apm_package=pkg, mcp_deps=[dep])) + _count, apm_config = run_mcp_integration( + **_base_kwargs(apm_package=pkg, mcp_deps=[dep], project_root=tmp_path) + ) assert apm_config == {"scripts": {"build": "echo hi"}, "targets": ["claude", "copilot"]} install_kwargs = mock_mcp.install.call_args.kwargs assert install_kwargs["apm_config"] == apm_config @patch(_PATCH_TARGET) - def test_forwards_singular_target_key_when_declared(self, mock_mcp): + def test_forwards_singular_target_key_when_declared(self, mock_mcp, tmp_path: Path): dep = MCPDependency(name="io.github.acme/server", transport="stdio") mock_mcp.deduplicate.side_effect = lambda x: x mock_mcp.install.return_value = 1 @@ -146,7 +151,9 @@ def test_forwards_singular_target_key_when_declared(self, mock_mcp): mock_mcp.get_server_provenance.return_value = {} pkg = _make_apm_package(target="claude") - _count, apm_config = run_mcp_integration(**_base_kwargs(apm_package=pkg, mcp_deps=[dep])) + _count, apm_config = run_mcp_integration( + **_base_kwargs(apm_package=pkg, mcp_deps=[dep], project_root=tmp_path) + ) assert apm_config == {"scripts": {}, "target": "claude"} assert "targets" not in apm_config From 439f46bcf51f64d155899fc01a54fdd80b7b2a06 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 15:07:30 +0200 Subject: [PATCH 3/3] test(install): align shared-file ownership expectation Update the cross-package cleanup integration test for the single last-writer ownership contract introduced by #2052, unblocking tagged release validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/integration/test_intra_package_cleanup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_intra_package_cleanup.py b/tests/integration/test_intra_package_cleanup.py index 2a94e9557..7405015b9 100644 --- a/tests/integration/test_intra_package_cleanup.py +++ b/tests/integration/test_intra_package_cleanup.py @@ -280,8 +280,8 @@ def test_shared_file_survives_when_other_package_still_deploys_it( for dep in local_deps_before if ".github/prompts/shared.prompt.md" in (dep.get("deployed_files") or []) ] - assert len(shared_claims) == 2, ( - "Both local packages must claim the shared prompt before testing cleanup" + assert {dep["name"] for dep in shared_claims} == {"pkg-b"}, ( + "Only the last writer may claim the shared prompt before testing cleanup" ) shared_target = temp_project / ".github" / "prompts" / "shared.prompt.md"