Manage dependencies
apm.yml is the manifest. apm.lock.yaml is generated by apm install.
You can either edit apm.yml by hand and run apm install, or use
apm install <pkg> to let the CLI append the entry for you. Either way
the lockfile is a generated artifact — never hand-edit it.
edit apm.yml -> apm install (manual: add or change a dep)apm install <pkg> -> (CLI mutates apm.yml + installs)edit apm.yml -> apm prune (remove a dep)The dependencies block
Section titled “The dependencies block”APM dependencies live under dependencies.apm in apm.yml. The mapping
shape is required — a flat list at the top level is rejected with an
explicit error pointing you at the structured form.
name: my-projectversion: 1.0.0
dependencies: apm: - microsoft/apm-sample-package#v1.0.0 - github/awesome-copilot/skills/review-and-refactordevDependencies has the exact same shape and is excluded from apm pack
output. Use it for tooling and tests:
devDependencies: apm: - my-org/internal-test-skillsFor the full manifest schema (every field, every type), see Package anatomy.
For MCP server entries (dependencies.mcp), see
Install MCP servers.
Reference formats
Section titled “Reference formats”Every entry under dependencies.apm is parsed by the same reference
parser. The supported forms:
| Form | Example | When to use |
|---|---|---|
| GitHub shorthand | owner/repo | Public GitHub repo, latest default branch. |
| Pinned ref | owner/repo#v1.0.0 | Pin to a tag, branch, or full commit SHA. |
| FQDN shorthand | gitlab.com/acme/repo#v2.0 | Any git host, not just github.com. |
| Virtual subdirectory | owner/repo/skills/review | Install one skill folder from a monorepo. |
| Virtual file | owner/repo/prompts/review.prompt.md | Install a single primitive file. |
| HTTPS git URL | https://gitlab.com/acme/repo.git | Explicit URL, any host. |
| SSH SCP-style | git@gitlab.com:acme/repo.git | SSH with default port. |
| SSH protocol | ssh://git@gitlab.com/acme/repo.git | SSH with explicit scheme or port. |
| SSH with non-default user | myuser@host:acme/repo.git or ssh://myuser@host/acme/repo.git | Honors a non-git SSH user from the URL — useful for Enterprise Managed User (EMU) accounts or any server where the SSH login is not git. Username is validated against ^[a-zA-Z0-9_][a-zA-Z0-9_.+-]*$ (64-char cap); percent-encoded userinfo is rejected. The username is presentation-only and not part of dependency identity. |
| Local path | ./packages/shared or /abs/path | Sibling package on disk. |
| Object form (git) | { git: <url>, path: <subpath>, ref: <ref>, alias: <name>, type: gitlab } | Aliases, nested groups, monorepo subpaths, bespoke GitLab hosts, or anything string forms cannot express. |
| Marketplace dict | { name: <plugin>, marketplace: <mkt>, version: <range> } | Install a plugin from a registered marketplace. Optional version accepts a semver range (e.g. ~2.1.0). Resolved to a concrete git ref at install time. |
| Registry shorthand | owner/repo#^2.0.0 with a default registry configured | Routes dep through the default registry instead of git. Default may come from apm.yml or ~/.apm/config.json. Requires registries experimental flag. |
| Registry object form | { id: owner/repo, version: ^2.0.0 } | Explicit registry dep. registry: optional when a default registry is configured. Requires registries experimental flag. |
Object form in YAML — three mutually exclusive keys select the variant
(git, path, or marketplace):
dependencies: apm: # Remote: git URL + optional sub-path, ref, alias - git: https://gitlab.com/acme/coding-standards.git path: instructions/security ref: v2.0 alias: security
# Self-managed GitLab on a bespoke hostname - git: https://code.acme.com/platform/standards.git type: gitlab
# Local: filesystem path (development only) - path: ./packages/shared-skills
# Remote monorepo sibling: inside owner/mono/packages/frontend/apm.yml - path: ../shared
# Marketplace: resolved to a concrete git ref at install time - name: sec-check marketplace: acme-plugins
# Marketplace with version constraint (semver range) - name: secrets-vault marketplace: acme-plugins version: "~2.1.0"
# Registry dep (experimental): whole package via default registry - id: acme/code-review-prompts version: ^2.0.0
# Registry dep (experimental): named registry, virtual sub-path - registry: corp-main id: acme/prompt-library path: prompts/review.prompt.md version: 1.4.0A path: declared inside a remote package is allowed only when the resolved
path stays inside that same cloned repo. APM expands it to the parent’s remote
host/repo/ref and downloads the sibling from the same origin. Absolute paths,
paths that escape the repo root, and cross-repo local paths are rejected.
This is for same-repo monorepo siblings, not general workspace semantics.
Use type: gitlab only on git object entries for self-managed GitLab
instances whose hostname does not make the platform obvious:
- git: https://code.acme.com/platform/standards.git type: gitlabThat routes REST file reads and token lookup through the GitLab path without relying on hostname heuristics. For the token precedence chain, see Authentication.
Generic non-default hosts do not receive APM-managed GitHub or GitLab PATs on
the HTTP file-read path. If a private host fails with 401/403, use a whole-repo
git dependency for full clone auth support, or choose the supported HTTP backend
signal (type: gitlab for GitLab-compatible hosts, GITHUB_HOST for GHES).
GitLab path: fetch transport: GitLab path: files are fetched over
git transport (not the REST API), so self-hosted instances with the API disabled
still install. See Authentication.
For private repos and non-GitHub hosts, see Private and org packages.
For registry-sourced dependencies (internal packages on Artifactory or a custom registry), see Registries.
Add a dependency
Section titled “Add a dependency”You have two paths.
CLI shortcut. Run apm install <pkg>. The CLI appends the entry to
dependencies.apm in apm.yml, then runs the full install pipeline. If
the pipeline fails (policy block, download error), the manifest is
atomically rolled back to its previous state.
apm install microsoft/apm-sample-package#v1.0.0See Install packages for the canonical install flow and full flag list.
Manual edit. Edit apm.yml yourself, then run apm install with
no arguments to sync.
apm installThe install command resolves the new entry, downloads it into
apm_modules/, updates apm.lock.yaml with the resolved commit and
content hash, and recompiles the deployed primitives for every target
harness. Critical security findings block the install; pass --force only
if you understand the risk. See Reference -> CLI commands
for the full flag list.
Pin a version
Section titled “Pin a version”Append #<ref> to a shorthand entry. <ref> can be a tag, branch, or
full commit SHA:
dependencies: apm: - microsoft/apm-sample-package#v1.0.0 # tag - acme/playbooks#main # branch (moves) - acme/playbooks#a1b2c3d4e5f6a7b8c9d0e1f234567890abcdef12 # SHA (immutable)For registry-sourced deps or when policy.dependencies.require_pinned_constraint: true is on, the ref slot also accepts semver constraints:
| Form | Example | Meaning |
|---|---|---|
| Bare exact | owner/repo#1.2.3 | Pinned to exactly 1.2.3. |
| Explicit equality | owner/repo#=1.2.3 | Same as bare exact (npm- and cargo-style). |
| Caret range | owner/repo#^1.2.3 | >=1.2.3, <2.0.0. |
| Tilde range | owner/repo#~1.2.3 | >=1.2.3, <1.3.0. |
| Bounded range | owner/repo#>=1.2.0 <2.0.0 | Explicit lower and upper bound. |
Pip-style ==1.2.3 is not part of the node-semver grammar APM follows and is rejected as an unbounded ref under require_pinned_constraint. Use =1.2.3 or the bare form instead.
Branches move; tags and SHAs do not. For reproducibility, prefer tags or
SHAs. The lockfile pins the resolved commit either way, so two clones
running apm install get the same bytes — but a branch ref will resolve
to a new SHA on the next apm update.
For SHA-pinned deps, think of apm update as Dependabot-style review
for AI packages: the manifest stays pinned to a commit, while the comment
shows the release tag. See apm update for
the full rewrite rules.
dependencies: apm: - acme/playbooks#b1c2d3e4f5a6b7c8d9e0f1234567890abcdef123 # v2.0.0apm update --dry-run previews the SHA/tag rewrite without changing the
manifest. Branches and lightweight tags are not accepted as revision-pin
update targets.
Marketplace ref override
Section titled “Marketplace ref override”When installing from a marketplace via the CLI, append #<ref> to
override the marketplace entry’s default source.ref:
apm install plugin@marketplace#v2.0.0In apm.yml, use the version field in the marketplace object form.
Semver ranges and bare versions (e.g. ~2.1.0, ^2.0, 2.1.0) are
resolved against git tags matching {name}--v{version} on the
marketplace repository. The highest matching tag is used.
Pin a semver range
Section titled “Pin a semver range”For git-source dependencies you can also pin a semver range as the ref. APM resolves the range against the remote’s tags at install time and records the concrete tag in the lockfile:
dependencies: apm: - acme/widget#^1.2.0 # any 1.x >= 1.2.0 - acme/widget#~1.4 # any 1.4.x - acme/widget#>=2.0 <3 # explicit range - acme/widget#1.5.x # wildcardAPM matches tags against v{version}, {name}--v{version}, and
{name}-v{version} patterns (with {version} as a bare-tag fallback) and
picks the highest tag that satisfies the range. For virtual subdirectory
deps, {name} is the final path segment (for example pkg-a in
acme/mono/packages/pkg-a). The original constraint is preserved in the
lockfile alongside the resolved tag, so apm install on a fresh clone
replays the same tag deterministically. A malformed range-like ref is
rejected; use a plain range such as ^1.2.0 or pin a literal tag such as
pkg-a-v1.2.0. Only apm update (or legacy apm install --update) or a
manifest change re-resolves to a newer tag.
Marketplace object-form ranges use the marketplace package name in the tag pattern:
dependencies: apm: - name: secrets-vault marketplace: acme-tools version: "~2.1.0"Remove a dependency
Section titled “Remove a dependency”- Delete the entry from
apm.yml. - Run
apm prune.
apm prune --dry-run # preview what gets deletedapm prune # delete orphaned packages from apm_modules/apm prune removes any directory in apm_modules/ that no longer
corresponds to a declared dependency. It does not touch your manifest,
your lockfile entries are rewritten on the next apm install, and
deployed files in .github/, .claude/, etc. are reconciled then too.
If you also want to refresh remaining deps to their latest refs, see Update and refresh.
The lockfile
Section titled “The lockfile”apm.lock.yaml is generated by apm install. It records, for every
dependency (direct and transitive):
- the resolved git commit SHA
- a SHA-256 content hash of the package
- the exact files deployed to your tree, with per-file hashes
Three rules:
- Commit it. A teammate cloning the repo and running
apm installgets byte-identical primitives only if the lockfile is in version control. - Do not hand-edit it. The file is regenerated on every install.
Any manual change is overwritten or, for hash fields, will trip
apm audit. - Inspect freely. It is plain YAML. Use it to answer “what version am I actually running?” without trusting the manifest, which may have floating refs.
For the full lockfile schema, see Package anatomy.
Explain a transitive dependency: apm deps why
Section titled “Explain a transitive dependency: apm deps why”After apm install, apm_modules/ may contain transitive packages that
you did not declare directly. To answer “who pulled this in?”, use
apm deps why <pkg>:
$ apm deps why shared-utils$ apm deps why acme-org/shared-utils$ apm deps why acme-org/shared-utils --json$ apm deps why -g shared-utilsThe lockfile is the source of truth — the command is
fully offline and walks the resolved_by parent chain bottom-up. The
lockfile records a single resolved parent per package, so the output is
one root-to-target chain (not a fan-out of every theoretical route):
[i] acme-org/shared-utils@1.4.2 (transitive)
acme-org/big-skills [declared in apm.yml] +-- acme-org/shared-utils<pkg> accepts four identifier styles, tried in order: unique key
(acme-org_shared-utils), full repo URL
(https://github.com/acme-org/shared-utils), owner/repo, or bare
basename (shared-utils) when unambiguous. An ambiguous bare name
exits 1 and lists the candidates.
Pass --json for scripting; the JSON document goes to stdout and all
logs / hints go to stderr, so apm deps why pkg --json | jq is safe:
{ "package": {"repo_url": "acme-org/shared-utils", "is_direct": false, "...": "..."}, "paths": [{"chain": [{"repo_url": "acme-org/big-skills", "is_direct": true}, ...]}]}Exit codes: 0 on success, 1 when the package is not installed or the
query is ambiguous, 2 when no lockfile exists yet (run apm install).
See also: apm deps tree for
the top-down graph view, and
apm deps info for full
metadata of one package.