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. |
| Aliased | owner/repo@my-alias | Install under a custom directory name. |
| Pinned + aliased | owner/repo#v1.0.0@my-alias | Combine the two. |
| 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> } | Escape hatch for nested groups, monorepo subpaths, or aliases that the string forms cannot express. |
| 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:
dependencies: apm: # Git dep with sub-path - git: https://gitlab.com/acme/coding-standards.git path: instructions/security ref: v2.0 alias: security
# Registry dep (experimental) — whole package via default registry - id: acme/code-review-prompts version: ^2.0.0
# Registry dep — named registry, virtual sub-path - registry: corp-main id: acme/prompt-library path: prompts/review.prompt.md version: 1.4.0For 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#a1b2c3d4e5f6... # 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.
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} and {name}--v{version} patterns
(with {version} as a bare-tag fallback) and picks the highest tag that
satisfies the range. The original constraint is preserved in the
lockfile alongside the resolved tag, so apm install on a fresh clone
replays the same tag deterministically. Only apm update (or legacy
apm install --update) or a manifest change re-resolves to a newer tag.
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.