Skip to content

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)

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-project
version: 1.0.0
dependencies:
apm:
- microsoft/apm-sample-package#v1.0.0
- github/awesome-copilot/skills/review-and-refactor

devDependencies has the exact same shape and is excluded from apm pack output. Use it for tooling and tests:

devDependencies:
apm:
- my-org/internal-test-skills

For the full manifest schema (every field, every type), see Package anatomy.

For MCP server entries (dependencies.mcp), see Install MCP servers.

Every entry under dependencies.apm is parsed by the same reference parser. The supported forms:

FormExampleWhen to use
GitHub shorthandowner/repoPublic GitHub repo, latest default branch.
Pinned refowner/repo#v1.0.0Pin to a tag, branch, or full commit SHA.
Aliasedowner/repo@my-aliasInstall under a custom directory name.
Pinned + aliasedowner/repo#v1.0.0@my-aliasCombine the two.
FQDN shorthandgitlab.com/acme/repo#v2.0Any git host, not just github.com.
Virtual subdirectoryowner/repo/skills/reviewInstall one skill folder from a monorepo.
Virtual fileowner/repo/prompts/review.prompt.mdInstall a single primitive file.
HTTPS git URLhttps://gitlab.com/acme/repo.gitExplicit URL, any host.
SSH SCP-stylegit@gitlab.com:acme/repo.gitSSH with default port.
SSH protocolssh://git@gitlab.com/acme/repo.gitSSH with explicit scheme or port.
SSH with non-default usermyuser@host:acme/repo.git or ssh://myuser@host/acme/repo.gitHonors 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/pathSibling 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 shorthandowner/repo#^2.0.0 with a default registry configuredRoutes 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.0

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.

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.

Terminal window
apm install microsoft/apm-sample-package#v1.0.0

See 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.

Terminal window
apm install

The 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.

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:

FormExampleMeaning
Bare exactowner/repo#1.2.3Pinned to exactly 1.2.3.
Explicit equalityowner/repo#=1.2.3Same as bare exact (npm- and cargo-style).
Caret rangeowner/repo#^1.2.3>=1.2.3, <2.0.0.
Tilde rangeowner/repo#~1.2.3>=1.2.3, <1.3.0.
Bounded rangeowner/repo#>=1.2.0 <2.0.0Explicit 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 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 # wildcard

APM 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.

  1. Delete the entry from apm.yml.
  2. Run apm prune.
Terminal window
apm prune --dry-run # preview what gets deleted
apm 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.

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:

  1. Commit it. A teammate cloning the repo and running apm install gets byte-identical primitives only if the lockfile is in version control.
  2. 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.
  3. 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>:

Terminal window
$ 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-utils

The 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.