Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions .apm/architecture/owners/contracts-tooling.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
"selectors": ["src/apm_cli/utils/yaml_io.py"],
"guards": ["contracts-tooling-frontmatter-yaml"]
},
{
"id": "read-only-lockfile-path",
"decision": "Read-only lockfile path resolution",
"owner": "deps/lockfile.py (resolve_lockfile_path_for_read)",
"selectors": ["src/apm_cli/deps/lockfile.py"],
"guards": ["contracts-tooling-lockfile-read"]
},
{
"id": "generated-content-footer-wording",
"decision": "Generated-content footer ownership wording",
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- `apm pack --check-clean` is now read-only and detects marketplace drift
without overwriting artifacts. Release pipelines that also produce artifacts
must run `apm pack` separately; see
[Releasing from any CI](docs/src/content/docs/producer/releasing-from-any-ci.md#the-canonical-sequence).
(by @danielmeppiel, closes #2727, #2730)
- Distributed `apm compile` now reconciles existing managed-section
`AGENTS.md` files without overwriting hand-authored content, generates new
placements safely, and never discovers, writes, or cleans content across
Expand Down
40 changes: 28 additions & 12 deletions docs/src/content/docs/producer/releasing-from-any-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ shell-script translation of these lines.
set -euo pipefail
VERSION="${VERSION:?VERSION must be set, e.g. v1.2.3}"

apm pack --check-versions --check-clean --json > pack-report.json
apm pack --check-versions --check-clean --json > gate-report.json
apm pack --json > pack-report.json

for f in build/*.zip .claude-plugin/marketplace.json; do
[ -f "$f" ] || continue
Expand All @@ -37,18 +38,27 @@ gh release create "$VERSION" \

What each command does:

- `apm pack --check-versions --check-clean --json` runs the pack with
the release gates enabled. `--check-versions` fails if per-package
- `apm pack --check-versions --check-clean --json` runs the read-only
release gates. `--check-versions` fails if per-package
versions disagree with `marketplace.versioning.strategy`.
`--check-clean` fails if the on-disk `marketplace.json` does not
match what a fresh pack would produce. `--json` writes a
machine-readable summary to stdout; human logs go to stderr.
- `apm pack --json` then writes the release artifacts after both gates
pass.
- `sha256sum` produces one sidecar per artifact. Consumers verify
with `sha256sum -c <file>.sha256`.
- `gh release create` uploads the bundle, the marketplace artifact,
and the sidecars under one tag. Use whichever release API your
forge exposes; the file set is what matters.

:::caution[Upgrading an existing release pipeline?]
`--check-clean` is now always read-only. If an older pipeline relied on one
`apm pack --check-clean` call to both validate and produce artifacts, split it
into the gate and pack calls shown above. Install one pinned apm-cli version
for the job so both calls use identical generation logic.
:::

Authenticate `gh` with a token that has `contents: write` on the
repo. Substitute the equivalent verb for non-GitHub forges
(`glab release create`, `az repos`, REST upload).
Expand Down Expand Up @@ -76,19 +86,21 @@ jobs:

[`microsoft/apm-action@v1`](https://github.com/microsoft/apm-action)
with `mode: release` is a convenience wrapper for the canonical
sequence above. It installs the CLI, runs `apm pack
--check-versions --check-clean --json`, generates the sidecars, and
calls `gh release create` against the pushed tag. Use it when you
sequence above. It installs the CLI, runs the read-only gates, packs
the release artifacts separately, generates the sidecars, and calls
`gh release create` against the pushed tag. Use it when you
want one less script to maintain; use the raw `run:` form below when
you need to customise any step.
you need to customise any step. The split gate-and-pack flow requires
apm-action `v1.10.0` or newer.

> **Reference deployment.** [`DevExpGbb/zava-agent-config`](https://github.com/DevExpGbb/zava-agent-config)
> runs this exact pipeline. The
> [v6.1.2 release](https://github.com/DevExpGbb/zava-agent-config/releases/tag/v6.1.2)
> attaches 7 per-plugin bundles + their `.sha256` companions +
> `marketplace-6.1.2.json` (15 assets total) via the workflow in
> [`.github/workflows/release.yml`](https://github.com/DevExpGbb/zava-agent-config/blob/main/.github/workflows/release.yml).
> APM `0.16.0` and apm-action `v1.9.1` or newer required.
> APM `0.16.0` or newer is required; use apm-action `v1.10.0` or newer
> for the split gate-and-pack flow documented here.

:::caution[Migrating release workflows from `.tar.gz`?]
The examples below assume the new `.zip` default from `apm pack --archive`.
Expand All @@ -102,7 +114,8 @@ artifact format.
with: { python-version: "3.12" }
- run: pip install apm-cli
- run: |
apm pack --check-versions --check-clean --json > pack-report.json
apm pack --check-versions --check-clean --json > gate-report.json
apm pack --json > pack-report.json
for f in build/*.zip .claude-plugin/marketplace.json; do
[ -f "$f" ] || continue
sha256sum "$f" > "${f}.sha256"
Expand All @@ -125,7 +138,8 @@ release:
- if: '$CI_COMMIT_TAG =~ /^v/'
script:
- pip install apm-cli
- apm pack --check-versions --check-clean --json > pack-report.json
- apm pack --check-versions --check-clean --json > gate-report.json
- apm pack --json > pack-report.json
- |
for f in build/*.zip .claude-plugin/marketplace.json; do
[ -f "$f" ] || continue
Expand All @@ -149,7 +163,8 @@ pipeline {
steps {
sh '''
pip install apm-cli
apm pack --check-versions --check-clean --json > pack-report.json
apm pack --check-versions --check-clean --json > gate-report.json
apm pack --json > pack-report.json
for f in build/*.zip .claude-plugin/marketplace.json; do
[ -f "$f" ] || continue
sha256sum "$f" > "${f}.sha256"
Expand All @@ -176,7 +191,8 @@ steps:
- task: UsePythonVersion@0
inputs: { versionSpec: "3.12" }
- script: pip install apm-cli
- script: apm pack --check-versions --check-clean --json > pack-report.json
- script: apm pack --check-versions --check-clean --json > gate-report.json
- script: apm pack --json > pack-report.json
- script: |
for f in build/*.zip .claude-plugin/marketplace.json; do
[ -f "$f" ] || continue
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/reference/cli/pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Bundles are target-agnostic. The consumer's project decides where files land at
| `--json` | off | Emit machine-readable JSON to stdout. All logs move to stderr. Shape: `{ok, dry_run, warnings, errors, marketplace: {outputs: [...]}}`. |
| `--legacy-skill-paths` | off | Bundle skills under per-client paths (e.g. `.cursor/skills/`) instead of the converged `.agents/skills/`. Compatibility flag. |
| `--check-versions` | off | Release gate: verify per-package versions agree with the configured `marketplace.versioning.strategy` (`lockstep`, `tag_pattern`, or `per_package`). Exits `3` on misalignment. Composes with `--check-clean` and `--dry-run`. |
| `--check-clean` | off | Release gate: regenerate every configured marketplace output to a temp representation and diff against the same effective path used by `apm pack`, including `--marketplace-path` overrides. Exits `4` for drift. Combine with `--dry-run` to compare without normal pack output generation. |
| `--check-clean` | off | Read-only release gate: regenerate every configured marketplace output to a temporary representation and diff against the same effective path used by `apm pack`, including `--marketplace-path` overrides. It never writes pack outputs and exits `4` for drift. |
| `--target`, `-t VALUE` | auto-detect | **Deprecated.** Recorded as informational `pack.target` metadata only; ignored by `apm install`. Will be removed in a future release. |

:::caution[Migrating automation from `.tar.gz`?]
Expand Down Expand Up @@ -240,7 +240,7 @@ Plugin manifest generation runs after BUNDLE and MARKETPLACE phases so the gener

| Code | Meaning |
|---|---|
| `0` | Success. Requested artifacts written (or, with `--dry-run`, planned). |
| `0` | Success. Requested artifacts written, planned with `--dry-run`, or validated without writes by `--check-clean`. |
| `1` | Build or runtime error: network failure, ref not found, no tag matches a marketplace range, lockfile read error, or unhandled packer exception. |
| `2` | `apm.yml` schema validation error. |
| `3` | `--check-versions` failed: per-package versions disagree with the configured marketplace versioning strategy. |
Expand Down
Loading
Loading