Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
21 changes: 13 additions & 8 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@
"version": "v9",
"sha": "373c709c69115d41ff229c7e5df9f8788daa9553"
},
"actions/github-script@v9.0.0": {
"repo": "actions/github-script",
"version": "v9.0.0",
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
},
"actions/upload-artifact@v7": {
"repo": "actions/upload-artifact",
"version": "v7",
"sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"
},
"github/gh-aw-actions/setup-cli@v0.71.2": {
"repo": "github/gh-aw-actions/setup-cli",
"version": "v0.71.2",
"sha": "ab8940d1df237fc4fae4ab8091e7ba66ada55a55"
},
"github/gh-aw-actions/setup@v0.71.2": {
"github/gh-aw-actions/setup@v0.71.5": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.71.2",
"sha": "ab8940d1df237fc4fae4ab8091e7ba66ada55a55"
"version": "v0.71.5",
"sha": "b8068426813005612b960b5ab0b8bd2c27142323"
},
"github/gh-aw/actions/setup@v0.50.6": {
"repo": "github/gh-aw/actions/setup",
Expand All @@ -59,6 +59,11 @@
"repo": "microsoft/apm-action",
"version": "v1.6.0",
"sha": "275e67418e97c26025852c7e91730cf4c11baf30"
},
"microsoft/apm-action@v1.7.1": {
"repo": "microsoft/apm-action",
"version": "v1.7.1",
"sha": "7a2f0d8b990341b4efb453d982d3df46b65e343b"
}
}
}
178 changes: 114 additions & 64 deletions .github/workflows/pr-review-panel.lock.yml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions .github/workflows/shared/apm.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Pre-agent-steps then download all bundles and restore them in one apm-action call.
#
# Source of truth: https://github.com/microsoft/apm/blob/main/.github/workflows/shared/apm.md
# apm-action pin: microsoft/apm-action@v1.6.0
# apm-action pin: microsoft/apm-action@v1.7.1
# To check whether a vendored copy is current, compare these two lines.
#
# Documentation: https://microsoft.github.io/apm/integrations/gh-aw/
Expand Down Expand Up @@ -254,7 +254,7 @@ jobs:
} >> "$GITHUB_OUTPUT"
- name: Pack APM packages
id: pack
uses: microsoft/apm-action@v1.6.0
uses: microsoft/apm-action@v1.7.1
env:
GITHUB_TOKEN: ${{ steps.token.outputs.token || secrets.GH_AW_PLUGINS_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
Expand Down Expand Up @@ -336,7 +336,7 @@ steps:
[ ${#list[@]} -gt 0 ] || { echo '::error::no apm bundles found'; exit 1; }
printf '%s\n' "${list[@]}" > /tmp/gh-aw/apm-bundle-list.txt
- name: Restore APM packages (all bundles)
uses: microsoft/apm-action@v1.6.0
uses: microsoft/apm-action@v1.7.1
with:
bundles-file: /tmp/gh-aw/apm-bundle-list.txt
---
Expand All @@ -349,7 +349,7 @@ in parallel one matrix replica per credential group, packs each group's packages
with `microsoft/apm-action`, and uploads a per-group bundle artifact. The agent
job's pre-agent-steps then download all bundles and restore them in a single
`apm-action` invocation (using the `bundles-file:` input shipped in
`microsoft/apm-action@v1.6.0`).
`microsoft/apm-action@v1.7.1`).

### How it works

Expand All @@ -363,7 +363,7 @@ job's pre-agent-steps then download all bundles and restore them in a single
3. **Restore** (agent pre-agent-steps): all `apm-*` artifacts are downloaded,
validated against the matrix manifest (defends against same-run artifact-name
collision attacks), and restored in one call via the `bundles-file:` input
on `microsoft/apm-action@v1.6.0`.
on `microsoft/apm-action@v1.7.1`.

### Authentication

Expand Down
180 changes: 115 additions & 65 deletions .github/workflows/triage-panel.lock.yml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.12.4] - 2026-05-07

### Fixed

- `apm install` now removes deployed files when a package is removed from `apm.yml`. Three sequential early-returns previously short-circuited the cleanup phase when the manifest was emptied; the orphan-cleanup logic itself was correct. (#1173)
- `apm audit --ci` no longer reports false drift on self-package primitives that link to repo-root files (`[..](../../FILE.md)`). The replay's in-package asset rewriter now re-anchors `target_location` onto `package_root` when the candidate sits outside the scratch tree, mirroring real-install output. (#1182)

## [0.12.3] - 2026-05-06

Expand Down
26 changes: 14 additions & 12 deletions apm.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ lockfile_version: '1'
generated_at: '2026-04-21T21:45:34.516938+00:00'
dependencies: []
local_deployed_files:
- .agents/skills/apm-review-panel
- .agents/skills/apm-strategy
- .agents/skills/apm-triage-panel
- .agents/skills/auth
- .agents/skills/cli-logging-ux
- .agents/skills/devx-ux
- .agents/skills/oss-growth
- .agents/skills/pr-description-skill
- .agents/skills/python-architecture
- .agents/skills/supply-chain-security
- .github/agents/agentic-workflows.agent.md
- .github/agents/apm-ceo.agent.md
- .github/agents/apm-primitives-architect.agent.md
Expand All @@ -13,6 +23,7 @@ local_deployed_files:
- .github/agents/oss-growth-hacker.agent.md
- .github/agents/python-architect.agent.md
- .github/agents/supply-chain-security-expert.agent.md
- .github/agents/test-coverage-expert.agent.md
- .github/instructions/changelog.instructions.md
- .github/instructions/cicd.instructions.md
- .github/instructions/cli.instructions.md
Expand All @@ -22,28 +33,19 @@ local_deployed_files:
- .github/instructions/linting.instructions.md
- .github/instructions/python.instructions.md
- .github/instructions/tests.instructions.md
- .github/skills/apm-review-panel
- .github/skills/apm-strategy
- .github/skills/apm-triage-panel
- .github/skills/auth
- .github/skills/cli-logging-ux
- .github/skills/devx-ux
- .github/skills/oss-growth
- .github/skills/pr-description-skill
- .github/skills/python-architecture
- .github/skills/supply-chain-security
local_deployed_file_hashes:
.github/agents/agentic-workflows.agent.md: sha256:d1ea2d038e2af8be11d6c95b3213b03b9777fae46f0438efa95d5a803e6c3765
.github/agents/apm-ceo.agent.md: sha256:82b259a9fbef2ba44acdb354921f23ee1caf4a70df2dc8bfddac64b1ede95630
.github/agents/apm-ceo.agent.md: sha256:484da64428ea46a6183dffd3f30c9fc5fc5c747639c0c79e55be69dba0899323
.github/agents/apm-primitives-architect.agent.md: sha256:6c01eab74ba18d70f21d45010d636cc6535d63cee81da12e61898d8036e0b028
.github/agents/auth-expert.agent.md: sha256:18264a933cba432b77d133e6ae11eee294c92ed245629af8c9b7a5bb7a9a300c
.github/agents/cli-logging-expert.agent.md: sha256:3ed7fe1a2e28e03a40311d4999ef54330908920d6515205708dd3f037abfcf0f
.github/agents/devx-ux-expert.agent.md: sha256:8310d130cca5bc548baf4a2a84e3c9680c9dc5d83a2718150636896ab2aa1f30
.github/agents/doc-analyser.agent.md: sha256:47b1d0204904b786c19d4fe84343e86cdab6f92f862f676ba741ffe6e1385679
.github/agents/doc-writer.agent.md: sha256:328a5b9ea079869b8ccd914a6e2135c204225a5eedb42f59a1ec73058f7f0b47
.github/agents/oss-growth-hacker.agent.md: sha256:1cd56bb78ab37d52c50e45ab69d759f775cd49cdf35981b3dc6c4004315c6b83
.github/agents/python-architect.agent.md: sha256:d268f86b55e137ecb09d137f6c3e12ecb9acceff34701c04f11dbdd1e08f828b
.github/agents/python-architect.agent.md: sha256:7587ee7c684c61046a83dfa1b7e39d1345f2f119c3395478e3ca2dbbaaaff0e9
.github/agents/supply-chain-security-expert.agent.md: sha256:8fb8cc426d6af17ba084a28b3f026c2b475b62e3ca63ed2f88b83bd823f877af
.github/agents/test-coverage-expert.agent.md: sha256:4df107de6179b5237fa9300582921e7fcb439928649601f0fef2d3b9275cea40
.github/instructions/changelog.instructions.md: sha256:1e51ec4c74e847967962bd279dc4c6e582c5d3578490b3c28d5f3acd3e05f73e
.github/instructions/cicd.instructions.md: sha256:9c0fafc74f743aa97e5adba2168d66c9e3a327b135065e3b804bdbb5f04cda5d
.github/instructions/cli.instructions.md: sha256:8e39e8d5047ce88575cb02f87c2bcede584dfef258bd86f7466c7badf136541a
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "apm-cli"
version = "0.12.3"
version = "0.12.4"
description = "MCP configuration tool"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
23 changes: 22 additions & 1 deletion src/apm_cli/compilation/link_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,29 @@ def _resolve_in_package_asset_link(
except PathTraversalError:
return None

# Replay-frame translation (#1182): during audit-replay of a
# self-package, ``ctx.base_dir`` is the scratch tmpdir but
# ``ctx.package_root`` (and therefore ``candidate``) still points
# at the real project tree. Computing ``relpath`` directly would
# produce a tmpdir-traversal link (e.g. ``../../../../Users/...``)
# that diverges from what real install writes to disk, causing
# spurious drift. Detect the cross-frame case (candidate outside
# base_dir) and re-anchor the target onto package_root so the
# rewrite mirrors the install-time output.
relpath_anchor = ctx.target_location
try:
relative_path = os.path.relpath(candidate, ctx.target_location)
candidate_in_base = candidate.is_relative_to(ctx.base_dir)
except (OSError, ValueError):
candidate_in_base = True
if not candidate_in_base:
try:
target_rel = ctx.target_location.relative_to(ctx.base_dir)
relpath_anchor = ctx.package_root / target_rel
except (OSError, ValueError):
relpath_anchor = ctx.target_location

try:
relative_path = os.path.relpath(candidate, relpath_anchor)
except (OSError, ValueError):
return None

Expand Down
66 changes: 66 additions & 0 deletions tests/unit/compilation/test_link_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,3 +750,69 @@ def test_compilation_does_not_rewrite_asset_links(self, base_dir):
)
# Unchanged: compile must not generalize.
assert content == result


class TestReplayFrameTranslation:
"""Regression for #1182: audit-replay must rewrite asset links as if
deployed to the project tree, not the scratch tmpdir.

Without the fix, ``apm audit --ci`` reports false drift on every
self-package install whose primitives contain ``../<repo-root-file>``
style links, because ``relpath`` is computed from the scratch
target_location while the candidate still resolves through the real
package_root.
"""

def test_replay_frame_rewrites_against_logical_target(self, tmp_path):
"""When candidate is outside base_dir (replay self-package), the
rewrite must re-anchor onto package_root so the link mirrors the
install-time output.
"""
# Real project tree (the actual repo).
real_root = tmp_path / "real_repo"
(real_root / ".apm" / "agents").mkdir(parents=True)
(real_root / "MANIFESTO.md").write_text("# Manifesto", encoding="utf-8")
(real_root / "apm.yml").write_text("name: self\n", encoding="utf-8")
source_file = real_root / ".apm" / "agents" / "x.agent.md"
source_file.write_text("placeholder", encoding="utf-8")

# Scratch tmpdir simulating replay's project_root.
scratch_root = tmp_path / "scratch"
scratch_target_dir = scratch_root / ".github" / "agents"
scratch_target_dir.mkdir(parents=True)
target_file = scratch_target_dir / "x.agent.md"

# Mirror replay wiring: base_dir = scratch, package_root = real repo.
resolver = UnifiedLinkResolver(scratch_root)
resolver.package_root = real_root

content = "See [`MANIFESTO.md`](../../MANIFESTO.md) for context."
result = resolver.resolve_links_for_installation(content, source_file, target_file)

# Must match what real install writes to disk: ../../MANIFESTO.md
# (relative from .github/agents/ back to repo root). NOT a tmpdir
# traversal like ../../../../tmp/.../real_repo/MANIFESTO.md.
assert "[`MANIFESTO.md`](../../MANIFESTO.md)" in result
assert "tmp" not in result
assert "real_repo/MANIFESTO.md" not in result

def test_normal_install_self_package_unchanged(self, tmp_path):
"""Sanity: normal install (base_dir == package_root parent) still
rewrites correctly and the replay-frame branch does not regress it.
"""
root = tmp_path / "repo"
(root / ".apm" / "agents").mkdir(parents=True)
(root / "MANIFESTO.md").write_text("# m", encoding="utf-8")
(root / "apm.yml").write_text("name: self\n", encoding="utf-8")
source_file = root / ".apm" / "agents" / "x.agent.md"
source_file.write_text("placeholder", encoding="utf-8")
target_dir = root / ".github" / "agents"
target_dir.mkdir(parents=True)
target_file = target_dir / "x.agent.md"

resolver = UnifiedLinkResolver(root)
resolver.package_root = root

content = "See [m](../../MANIFESTO.md)."
result = resolver.resolve_links_for_installation(content, source_file, target_file)
assert "[m](../../MANIFESTO.md)" in result
Loading