You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add apm (Agent Package Manager) ecosystem support - #16382
This PR adds a new apm ecosystem to Dependabot, adding support for bumping APM (Agent Package Manager) dependencies declared in an apm.yml manifest.
APM is a git-based package manager for AI agent context — skills, prompts, chat modes, instructions and other agent primitives. Because every APM dependency resolves to a git ref, this ecosystem bumps versions the same way the GitHub Actions and git submodules ecosystems do: it resolves the newest semver tag on the remote via Dependabot::GitCommitChecker and rewrites the manifest ref.
dependencies:
apm:
- microsoft/edge-ai#v1.0.0 # GitHub shorthand pinned to a tag
- gitlab.com/acme/prompts#v2.1.0 # FQDN shorthand for any git host
- octo-org/octo-skills/skills/review#v1.4.0 # virtual sub-path within a repo
For each string-shorthand git dependency pinned to a semver tag, Dependabot:
Resolves the git remote (https://<host>/<owner>/<repo>).
Finds the highest semver tag honoring the update / cooldown / ignore rules (reusing GitCommitChecker).
Rewrites only the ref in apm.yml (e.g. #v1.0.0 → #v1.4.0), preserving the rest of the declaration byte-for-byte.
devDependencies.apm entries are also updated and flagged non-production via the development group.
Scope of this first iteration (manifest-only)
To keep this PR small and reviewable, the following are intentionally out of scope — ignored, never modified, never erroring — and documented as follow-ups in apm/README.md:
Object-form entries (git: / registry: / id: / path: maps) and mcp: entries — only the string shorthand is parsed.
Branch- and SHA-pinned entries — these are resolved by APM's own lockfile, which Dependabot does not regenerate.
Local path entries (./pkg, ../pkg, /pkg).
Lockfile writing — apm.lock.yaml is read (to report the package-manager version) but not rewritten; APM regenerates it after a manifest change.
Implementation
Full ecosystem implementation, modeled on github_actions (semver-tag resolution) blended with git_submodules (git-based, cooldown):
Adds beta support for updating semver-tagged APM dependencies in apm.yml, including parsing, git-tag resolution, manifest updates, packaging, and CI integration.
Changes:
Implements the complete APM ecosystem and tests.
Adds fixtures and user documentation.
Wires APM into builds, updater, omnibus, Sorbet, CI, and smoke-test configuration.
This hard-codes the SSH user to git, so valid APM SCP references such as myuser@host.example.com:owner/repo.git#v1.0.0 fall through shorthand parsing and are skipped. APM accepts validated non-default SSH usernames; match that username grammar while continuing to discard it from dependency identity.
Treating the first two path segments as owner/repo points documented nested-group URLs such as git@gitlab.com:group/subgroup/repo.git#v1.0.0 at group/subgroup and misclassifies repo as a virtual path. Preserve the full repository path for explicit .git URLs, and use host-aware boundary handling where shorthand can be ambiguous.
git_url always changes the parsed transport to HTTPS and treats any port as an HTTPS port. For example, the accepted ssh://git@gitlab.com:2222/acme/prompts.git#v1.0.0 becomes https://gitlab.com:2222/acme/prompts, so tag lookup sends TLS to the SSH port and fails; explicit HTTP endpoints are similarly changed. Preserve the parsed scheme/port in the clone URL, or reject transports that this ecosystem cannot resolve instead of parsing them as supported.
Report beta feature disabled instead of missing apm.yml
apm/lib/dependabot/apm/file_fetcher.rb:37
When beta ecosystems are disabled, this reports that apm.yml is missing even when it exists, hiding the actual remediation from users. Use a beta-feature message here rather than required_files_message, as the ecosystem scaffold does.
The default DependencySet is case-insensitive, so distinct APM identities that differ only by case are merged. This is especially concrete for GitHub virtual paths: org/repo/skills/Review and org/repo/skills/review target distinct case-sensitive paths but collapse to one dependency and can have both refs rewritten together; on a case-sensitive custom host, differently cased repositories can instead trigger Dependency#source_details’s “Multiple sources!” error. Preserve case-sensitive virtual/repository identity here, applying host-specific normalization only to repository components known to be case-insensitive.
This silently changes explicit HTTP remotes to HTTPS. URL_STYLE accepts http://git.internal/..., but git_url always returns https://git.internal/...; an HTTP-only APM source is therefore queried at the wrong remote and its update fails (or resolves unrelated HTTPS content). Either retain the parsed transport for HTTP URLs or reject that URL form as unsupported instead of emitting a different source.
This explicitly enables YAML aliases, but OpenAPM v0.1 requires consumers to reject anchors/aliases. It also creates inconsistent behavior here: safe_load accepts the document while apm_entries_in sees a Psych::Nodes::Alias instead of a sequence and silently returns no dependencies. Leave aliases disabled so the existing Psych::BadAlias rescue reports the manifest as unparseable.
The current APM manifest contract routes plain string shorthands with a selector through registries.default when one is configured. This parser ignores that setting and always constructs a Git remote, so a valid registry dependency such as owner/pkg#1.2.3 can be queried and updated as an unrelated GitHub repository. Since registry support is out of scope, detect this configuration and skip registry-routed shorthand entries (while retaining explicit Git URL/host forms).
Parse SemVer build metadata in ignore requirements
apm/lib/dependabot/apm/requirement.rb:31
This still delegates requirement versions to Gem::Requirement, although Apm::Version explicitly supports SemVer build metadata that RubyGems cannot parse. An ignore such as = 1.2.0+build.5 therefore raises BadRequirementError instead of filtering the tag, so the promised ignore handling breaks for a supported APM version. Parse requirement operands into Apm::Version with a build-aware pattern, as the Helm requirement implementation does.
Addresses two Copilot review findings on the apm ecosystem:
- file_updater: a manifest ref bump left the fetched apm.lock.yaml stale, so
APM's ref-consistency check would fail until the lockfile ref matched the
manifest. The updater now rewrites each bumped package's `ref:` value in the
lockfile in place (located by its YAML source span) and emits the updated
lockfile. `resolved`/`integrity` are intentionally left untouched: the
integrity hash cannot be recomputed offline and moving `resolved` alone would
only swap a ref/resolved mismatch for a resolved/integrity one; both are
regenerated together by `apm install --update`. The lockfile is now fetched
as a regular (non-support) file so an updated copy is included in the PR.
- package_manager: the apm CLI version recorded as `apm_version` in the lockfile
is written from apm's Python (PEP 440) metadata and may not be strict SemVer
(e.g. `0.32.0rc1`), which made the strict Apm::Version raise ArgumentError.
Parse it with the permissive base Dependabot::Version, falling back to the
default when the string cannot be represented.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removing the `support_file = true` assignment in the previous commit left
`file_fetcher.rb` with no untyped calls, so Sorbet's `spoom srb bump
--from strict --to strong` check (run in CI) now requires it to be raised to
`# typed: strong`. `file_updater.rb` and `file_parser.rb` stay `# typed: strict`
because they walk the YAML AST via Psych, whose nodes are untyped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-7 review (Copilot findings L & M) flagged that the lockfile updater
targeted a fabricated `packages:`/`name:`/`ref:` schema that does not exist in
real `apm.lock.yaml` files, and that the README "Scope" section still described
the lockfile as read-only.
Finding L — rewrite the updater against the schema apm actually emits (verified
against microsoft/apm's apm-cli):
- Parse the top-level `dependencies:` sequence (not `packages:`).
- Match each entry by its normalised `repo_url` (plus `virtual_path` for
monorepo sub-packages), canonicalised through `PackageSpecifier` — the same
parser that derives `dependency.name` from the manifest — never by the
self-asserted `name:` field.
- Rewrite `resolved_ref:` (not `ref:`) to the new tag; leave `resolved_commit:`
and `content_hash:` for `apm install --update` to re-pin, since `content_hash`
cannot be recomputed offline.
Finding M — update the README "Scope" section to describe the partial lockfile
write (resolved_ref kept consistent; resolved_commit / content_hash regenerated
later) instead of claiming the lockfile is never rewritten.
Fixtures (apm.lock.yaml, apm_lock.json) and specs are rewritten to the real
schema. Validated in Docker: turbo_tests2 145/0, RuboCop 0, Sorbet 0 apm errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t + repo_url
Round-8 review (Copilot findings) raised two HIGH issues.
Finding: register APM in the production updater. `updater/lib/dependabot/setup.rb`
expanded the Sentry app-path filter for apm but never added the ecosystem
`require`, so `dependabot/apm` was not loaded by production/dry-run entrypoints
and APM jobs would fail as an unregistered package manager. Add
`require "dependabot/apm"` to the ecosystems block.
Finding: build the lockfile identity from host + repo_url. Real apm.lock.yaml
entries store the repository path host-blind (e.g. `repo_url: acme/prompts`) with
the host in a separate `host:` field, mirroring apm-cli's
`build_dependency_unique_key`, which host-qualifies the key for non-github hosts.
`lockfile_identity` parsed `repo_url` alone, so a gitlab entry canonicalised to
`acme/prompts` and never matched the manifest dependency `gitlab.com/acme/prompts`,
leaving `resolved_ref` stale. Feed the entry's `host` as the default host to
`PackageSpecifier` (the same parser that derives `dependency.name`), so the
reconstructed identity matches. Fixtures now use the split representation
(github repos host-blind; the gitlab dep carries a separate `host:`), and a new
spec covers host-qualified matching.
Validated in Docker: turbo_tests2 147/0, RuboCop 0, Sorbet 0 apm errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewriting only the lockfile's resolved_ref to the bumped tag is actively
harmful. APM's install path compares the manifest ref to resolved_ref via
detect_ref_change; once they match, it stops detecting drift and replays the
stale resolved_commit, so a normal 'apm install' keeps installing the old
commit. A full regeneration is not possible offline either, because
content_hash (the hash of the materialised package tree) cannot be recomputed
without downloading and building the package.
Revert the lockfile-writing feature and treat apm.lock.yaml as a read-only
support file (fetched only to report the APM CLI version). Leaving the entry
untouched means the bumped manifest ref no longer matches resolved_ref, so APM
detects the drift and re-resolves the tag, regenerating resolved_commit and
content_hash correctly on the next 'apm install'.
- file_fetcher.rb: fetch apm.lock.yaml as a support file again (typed: strict)
- file_updater.rb: remove all lockfile-writing logic; manifest-only (typed: strong)
- README: document that the lockfile is intentionally left unchanged
- specs: drop lockfile-update contexts; assert the lockfile is a support file
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Requirement bounds were parsed by Gem::Requirement, which rewrites a
prerelease operand such as 1.0.0-alpha.beta into the RubyGems form
1.0.0.pre.alpha.beta and compares it with RubyGems precedence. That orders
prerelease identifiers the opposite way to SemVer, so ignore/security ranges
like '< 1.0.0-alpha.beta' wrongly excluded 1.0.0-alpha.1.
Override Requirement.parse (as cargo/bun/go_modules do) to build each bound
from an Apm::Version when the operand is strict SemVer, so satisfied_by? uses
the SemVer comparator already implemented in Apm::Version. Partial operands
(e.g. '>= 1.0', '>= 0') that Apm::Version cannot represent fall back to
RubyGems parsing, which has no prerelease-ordering issue.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address three "previously missed" observations from Copilot review that
could make valid apm.yml inputs fail an update or resolve against the
wrong source:
- Skip YAML block scalars (folded `>` / literal `|`) during parsing. Their
decoded value is not a contiguous substring of the raw source span, so the
updater found no substitution and jobs failed with "Expected content to
change!". They are now excluded at parse time, like object-form entries.
- Skip string-shorthand dependencies when the manifest configures a default
registry (`registries.default`). APM routes shorthand through the registry
rather than Git, so bumping them against a git remote would rewrite from
the wrong version source. Registry resolution is out of scope for v1;
explicit clone URLs are never registry-routed and are still updated.
- Accept any SSH user in SCP-style refs (e.g. `myuser@host:owner/repo`), not
only `git@`, matching APM's supported username grammar. The user is
discarded, consistent with the ssh:// URL form.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address two Copilot review findings:
- Case-sensitivity (file_parser.rb): the dependency set was
case-insensitive, but only GitHub owner/repo paths are case-insensitive
-- other git hosts (e.g. GitLab) are case-sensitive. Distinct repos such
as gitlab.com/Group/Repo and gitlab.com/group/repo therefore collapsed
into one dependency and could be bumped using the wrong repo's tags.
PackageSpecifier now case-folds GitHub owner/repo only (preserving casing
for other hosts and for virtual sub paths), and the parser builds a
case-sensitive DependencySet.
- Non-APM image startup (Dockerfile.updater-core): setup.rb requires
dependabot/apm unconditionally, but apm was missing from ECOSYSTEM_LIST,
which creates placeholder entrypoints for ecosystems whose source is not
copied into a given image. Without it, every non-APM updater image would
fail startup with LoadError: cannot load such file -- dependabot/apm.
Add apm to the list.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Escaped double-quoted YAML scalars (e.g. "owner/repo\x23v1.0.0") decode to a
value that is not a contiguous substring of their raw source span, so the file
updater could not locate and rewrite the ref and the job failed with "Expected
content to change!".
Add FileParser#scalar_round_trips? to skip any scalar whose decoded value is
not present verbatim in its source span, mirroring the updater's
`original.include?(declaration)` guard. This complements the existing
block-scalar skip: block scalars are still excluded by style, while flow
scalars are now additionally checked so escaped entries are dropped at parse
time instead of producing a failing update job.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-14 review noted that the "What Dependabot updates" scope promises every
pinned string shorthand is updated, but block scalars and escaped quoted
scalars are silently skipped by the parser. Add an explicit out-of-scope bullet
covering YAML block scalars (folded/literal) and quoted scalars that rely on
escape sequences, explaining why their refs cannot be rewritten in place and
pointing users at the plain/simply-quoted spelling that is updated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-15 review: PackageSpecifier#name hard-coded github.com as the only
implicit host, so a manifest that configures a custom default_host (e.g.
git.internal.example) produced names like "git.internal.example/team/skills"
instead of the canonical "team/skills". That gives the dependency a different
identity from APM, so dependency-name ignore rules and DependencySet
deduplication would not match.
Thread the effective default host through parse -> build -> initialize and
compare against it in #name, so the manifest-selected default host is stripped
while any explicit non-default host is still qualified. Comparing by value also
collapses the shorthand and an explicit spelling of the same default host to a
single identity.
Also mount the apm gemspec/lib/script/spec in bin/docker-dev-shell so the
documented "bin/docker-dev-shell apm" workflow exercises the working copy
instead of the stale baked-in source.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM resolves GitHub Enterprise Cloud data-residency hosts (*.ghe.com) as
GitHub, so virtual package paths like acme.ghe.com/org/repo/skills/review
should split into owner/repo plus a virtual sub-path rather than treating
the whole path as the repository. Add a github_family?(host) classifier
(github.com or *.ghe.com) and use it for the owner/repo boundary and
owner/repo case-folding in PackageSpecifier. Self-hosted GHES on an
arbitrary hostname cannot be recognised from the host alone and remains a
follow-up. Update the class docs, README and specs accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two robustness fixes for uncommon manifest entries:
- Azure DevOps Services (dev.azure.com, ssh.dev.azure.com and legacy
*.visualstudio.com) resolves repositories to org/project/_git/repo clone
URLs, which PackageSpecifier's generic host/owner/repo builder cannot
construct. It previously emitted a wrong remote (e.g.
https://dev.azure.com/org/project/repo) that GitCommitChecker would query
as a non-repository endpoint. Detect ADO hosts and skip them (return nil)
rather than resolve to a wrong remote; native _git clone-URL support is a
documented follow-up.
- A quoted scalar may legally carry trailing whitespace after the ref (e.g.
"owner/repo#v1.0.0 "). PackageSpecifier strips it before reading the ref,
but the stored declaration keeps it, so the updater's end-anchored
"#<ref>" check missed and failed the whole update with "Expected content
to change!". Anchor the match before optional trailing whitespace and
preserve that whitespace in the rewrite.
Update the class docs, README scope and specs accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explicit clone URLs resolve to an https://host/owner/repo remote because
Dependabot enumerates tags over HTTPS with a token. The host retained any
port from the source URL, so ssh://git@host:2222/org/repo.git became
https://host:2222/org/repo -- HTTPS on the SSH port, which GitCommitChecker
cannot reach. A port is only reusable when the source itself is https (it
names the same endpoint); http, ssh and git ports do not, so strip them in
split_host_and_path and keep only https ports. Document the transport/port
handling and add specs for ssh/https/http custom-port URLs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two endpoint-parsing fixes from review:
- Host-family classification (github_family?, azure_devops_host?) keyed off
the full host, which now can carry a port for https sources. A URL such as
https://github.com:8443/org/repo/skills/review was then treated as
non-GitHub, so the virtual path folded into the repository and Dependabot
queried the wrong remote. Classify by hostname (port stripped) while git_url
and name keep the full authority.
- Explicit clone URLs resolve over https, but http:// and git:// name a
different endpoint (a distinct port, and for http an unencrypted service).
Silently rewriting them to https queried a possibly-wrong remote, so skip
them (out of scope for v1) instead. https keeps its authority incl. port;
ssh:// and SCP still resolve over https on the same host, dropping the ssh
port.
Also align shorthand? with APM's _is_explicit_git_form: a .git-suffixed ref
is an explicit git form (Git escape hatch), so it is not registry-routed even
without a transport scheme. Document the transport/port rules and add specs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DependencySet merges repeated declarations of the same package into one
dependency with several requirements, whose refs need not be equal. The
update tag is selected from the dependency's combined (lowest) version, but
updated_requirements rewrote every semver-pinned requirement to that tag
unconditionally. With refs v1.0.0 and v2.0.0, a security fix at v1.5.0 (or a
latest capped by an ignore rule) therefore downgraded the v2.0.0 declaration.
Skip any requirement whose own ref is already at or above the selected tag, so
a lower selected tag never rewrites a higher declaration downward. Add a
mixed-ref regression spec covering the bump-lower / keep-higher behaviour.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two latent crashes from review:
- URI schemes are case-insensitive (RFC 3986), but URL_STYLE and the scheme
dispatch only matched lowercase. HTTPS://gitlab.com/acme/repo.git fell
through to shorthand parsing and became a bogus default-host repository
(https://github.com/https:/gitlab.com). Match the scheme case-insensitively
and normalise it before dispatch.
- Apm::Version enforces a strict MAJOR.MINOR.PATCH triple, but Gem::Requirement
evaluates ~> by calling bump on the operand, and Gem::Version#bump rebuilds
self.class from a shortened value (1.2.3 -> 1.3). Apm::Version rejected that
partial value, so an ignore rule like ~> 1.2.3 raised while filtering tags.
Override bump to return the SemVer upper bound (x.(y+1).0), so ~> 1.2.3
evaluates as >= 1.2.3, < 1.3.0.
Add specs for uppercase https/ssh/http URLs, Version#bump, and satisfied_by?
on a pessimistic constraint.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve the four "previously missed" findings from the Copilot re-review,
each verified against the microsoft/apm source:
- Preserve production provenance (file_parser.rb / apm.rb). Production
declarations now carry an explicit "dependencies" marker group instead of an
empty list, and the registered production check is presence-based
(`groups.empty? || groups.include?("dependencies")`). When DependencySet
merges a package declared in both `dependencies.apm` and `devDependencies.apm`,
Dependency#production? flattens every requirement's groups; the explicit
marker now survives the flatten so the dependency stays production instead of
being dragged non-production by the dev-only declaration.
- Break equal-version ties deterministically (git_commit_checker.rb). Build
metadata is not part of SemVer precedence (matching APM's SemVer), so tags
like `v1.3.0+build.5` and `v1.3.0+build.9` compare equal and the inherited
`max_by` kept whichever the remote advertised first. `max_local_tag` now
breaks the tie on the highest full tag string, as APM does, so latest-tag
selection is deterministic. The shared `max_local_tag` sig is marked
`overridable` (it is already a public seam used by other ecosystems).
- Support package-scoped SemVer tags (git_commit_checker.rb). Besides plain
`v?<semver>` tags, APM's DEFAULT_TAG_PATTERNS also resolve `{name}_v{version}`,
`{name}--v{version}` and `{name}-v{version}`, where `{name}` is the package's
own name (repository name or final virtual-path component). Tag recognition
and version extraction now parse these forms, scoping the accepted prefix to
this dependency's package name so a sibling package's tag never resolves.
- Strip custom ports from the dependency identity (package_specifier.rb). APM's
dedup/lock key excludes the port and keeps it only as clone-URL transport, so
`name` now strips the port while `git_url` retains it, keeping duplicate
declarations and dependency-name ignore rules aligned with APM identity.
Adds regression specs and two upload-pack fixtures (build-metadata ties and
package-scoped tags); updates the README. Validated in Docker: apm suite
196/0, RuboCop clean, Sorbet apm 0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-22 taught Apm::GitCommitChecker to resolve APM's package-scoped tags
(`{name}_v…`, `{name}--v…`, `{name}-v…`), but the file parser and update
checker still gated refs on the raw `Apm::Version.correct?`, which only accepts
plain `v?<semver>`. As a result a dependency pinned to `review--v1.0.0` was
never parsed into the dependency set, and even when admitted its manifest
declaration was left unrewritten because `updated_requirements` skipped it (and
`Version.new(current_ref)` would have raised on the scoped ref).
Consolidate APM's tag grammar into a single reader, `Apm::Version.semver_from_ref`,
that returns the SemVer core of a ref (plain or package-scoped, scoped to the
package's own name) or nil, and route every consumer through it:
- git_commit_checker.rb now delegates tag recognition and version extraction to
the shared reader, dropping its private copy of the scoped-tag parsing.
- file_parser.rb admits scoped-tag entries and records the extracted SemVer core
as the dependency version, while keeping the original ref in the requirement
so the updater rewrites the manifest verbatim.
- update_checker.rb normalises each requirement's ref through the reader before
validating and comparing it, so scoped-tag declarations are bumped instead of
silently left behind.
Adds unit coverage for the shared reader plus parser and update-checker specs
that exercise a package-scoped ref end to end. Validated in Docker: apm suite
204/0, RuboCop clean, Sorbet apm 0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-22 taught PackageSpecifier to keep a custom port in a dependency's clone
URL (`github.com:8443` is a distinct endpoint). The metadata finder, however,
keyed the provider lookup on the host *including* the port, so a custom-port
GitHub or GitLab URL missed `PROVIDER_BY_HOST` and fell through to the generic
`Source.from_url`. That fallback's unanchored `github.com:` / `gitlab.com:`
regexes then read the port as the repository -- `github.com:8443/org/repo`
resolved to repo `8443/org` -- producing wrong source and changelog links.
Classify the provider by `hostname_without_port` so a custom port no longer
changes the match, and when a port is present build the Source directly with the
full authority preserved as its hostname plus a matching self-hosted-style API
endpoint (`https://{authority}/api/v3` for GitHub, `/api/v4` for GitLab, mirroring
Dependabot::Source's GitHub Enterprise handling) rather than the public SaaS
defaults. Portless canonical hosts keep the provider defaults exactly as before.
Adds metadata-finder specs for custom-port GitHub and nested-namespace GitLab
URLs. Validated in Docker: apm suite 206/0, RuboCop clean, Sorbet apm 0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses three review findings on the apm ecosystem.
1. Preserve non-default HTTPS ports in the dependency identity
(package_specifier.rb). APM's get_identity keeps a non-default port in the
identity (`github.com:8443/owner/repo`) and normalises only the default
`:443` away. PackageSpecifier#name stripped every port, so
`git.example.com:8443/org/repo` and `:9443/org/repo` collapsed into one
DependencySet entry; holding two different clone URLs, that merged dependency
then raised `Multiple sources!`. #name now keeps a non-default port (matching
APM), and the default `:443` is normalised off when parsing the HTTPS URL so
`git_url` and `name` share one authority and a `:443` pin dedups with a
portless one.
2. Resolve the update tag per requirement source (update_checker.rb). A merged
dependency's requirements need not share a tag family (e.g. `review--v1.0.0`
and `review-v1.0.0`); the checker filtered candidates by only the first
requirement's ref, so the second declaration missed its newer tag or was
rewritten into the first family. updated_requirements now resolves each
requirement through a GitCommitChecker scoped to that ref, all sharing one
injected GitMetadataFetcher so the upload pack is fetched once, mirroring the
GitHub Actions checker.
3. Report disabled beta ecosystems instead of a missing manifest
(file_fetcher.rb). When beta ecosystems are off, fetch_files raised
"apm.yml not found" even when the manifest exists. It now raises with an
actionable message telling the user to set `enable-beta-ecosystems: true`.
Adds specs for non-default and `:443` ports, the mixed-family multi-requirement
bump, and the beta remediation message. Validated in Docker: apm suite 208/0,
RuboCop clean, Sorbet apm 0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The interpolated form (`#{MANIFEST_FILENAME}` + `.freeze`) tripped a conflict
between `# typed: strict`, which requires a `T.let` annotation for a constant
whose value Sorbet cannot infer (Sorbet 7027), and RuboCop's
`Sorbet/RedundantTLetForLiteral`, which rejected the `T.let`. An interpolated
string is not an inferable literal, so neither form satisfied both tools.
Inline the manifest filename so the constant is a plain string literal, matching
the sibling MANIFEST_FILENAME/LOCKFILE_FILENAME constants: `# frozen_string_literal`
freezes it, Sorbet infers String without `T.let`, and RuboCop is satisfied.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses four review findings.
1. Normalize the HTTPS port in host-qualified shorthand (package_specifier.rb).
The `host.com/org/repo` shorthand branch skipped port normalization, so
`github.com:443/org/repo` kept `:443` in its name while the explicit-URL and
portless forms normalized to `org/repo` -- splitting duplicate declarations
into separate dependencies and giving ignore rules inconsistent names. The
shorthand authority now runs through `normalize_https_port` too (default
`:443` dropped, non-default ports kept as distinct endpoints).
2. Compute update availability across all requirement families
(update_checker.rb). `latest_version`/`lowest_security_fix_version` resolved
only through the first merged requirement's ref family, so if only a
non-first family had a newer tag the base `can_update?` returned false and
that declaration was never updated. Both now aggregate over every
requirement-scoped checker (max latest / min security fix), so an update in
any family is detected.
3. Filter security tags against each requirement's own version
(update_checker.rb). A requirement-scoped checker still filtered fixes
against the merged dependency's lowest version, so a `v2.0.0` pin was handed
the `v1.1.0` fix and then skipped by the downgrade guard, staying vulnerable.
Security-fix selection now filters relative to each requirement's parsed
`ref_version`, so `v2.0.0` reaches its own `v2.1.0` fix.
4. Register apm in bin/dry-run.rb. The script added `apm/lib` to the load path
but never required `dependabot/apm`, so dry-run runs treated apm as an
unknown package manager. Added the require alongside the other ecosystems.
Adds the `apm-package-two-lines` upload-pack fixture plus specs for shorthand
port normalization, cross-family latest-version detection, and per-line security
fixes. Validated in Docker: apm suite 212/0, RuboCop clean, Sorbet apm clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 28 follow-up to the per-requirement security-fix handling.
`DependencySet` merges repeated declarations of a package into one dependency
whose single `version` is the LOWEST pinned ref, and base `UpdateChecker#vulnerable?`
only inspects that value. So a dependency declared at both `v1.0.0` and `v2.0.0`
with an advisory on `>= 2.0.0, < 2.1.0` was reported not-vulnerable (it checked
`v1.0.0`), the security update was skipped upstream in
`create_security_update_pull_request`, and the genuinely vulnerable `v2.0.0`
declaration was never fixed.
- Override `vulnerable?` to also flag the dependency when ANY requirement's own
ref version is affected (mirrors the npm_and_yarn/bun override that consult
`vulnerable_versions`), so the security update is no longer skipped.
- `resolved_tag_for_requirement` now decides per requirement: an affected
declaration moves to the lowest fix above its OWN version, while an unaffected
declaration is left untouched during a security update (updates stay minimal)
and still bumps to latest during a normal version update.
- `fetch_lowest_security_fix_version` only considers requirement families that
are themselves vulnerable, so an unaffected line no longer contributes a
spurious fix version.
Adds specs: `#vulnerable?` is true when only the higher merged declaration is
affected (and false when none is), and `#updated_requirements` fixes only the
affected `v2.0.0` line (to `v2.1.0`) while leaving `v1.0.0` untouched. Validated
in Docker: apm suite 215/0, RuboCop clean, Sorbet apm clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 29 follow-up. `fetch_latest_version` intentionally reports the maximum
tag reachable by ANY requirement family so the base `can_update?` gate fires
when only a higher family can move (the round-26 cross-family fix). But that
same value flowed through `preferred_resolvable_version` into
`updated_dependency_with_own_req_unlock`, which sets the resulting dependency's
`version`.
`DependencySet` defines a merged dependency's version as its LOWEST pin, and
`updated_requirements` can legitimately leave families on different tags (e.g.
`review-v1.4.0` alongside `review--v1.5.0`). Reporting the max there desynced
the resulting version -- and its derived update type and PR metadata -- from the
rewritten requirements: a `1.5.0` update was reported while the merged
dependency is really `1.4.0`.
Override `updated_dependency_with_own_req_unlock` to derive the resulting
version from the post-update refs using the same lowest-pin rule, keeping the
"any family can update" gate (max) separate from the reported version (lowest).
`latest_version` is unchanged, so the gate still fires. Unified the ref-version
extraction into `pinned_versions(requirements)`, reused by both `vulnerable?`
(pre-update refs) and the new version derivation (post-update refs).
Adds a spec: a merged dependency whose families resolve to `review-v1.4.0` and
`review--v1.5.0` reports version `1.4.0` while still rewriting both pins.
Validated in Docker: apm suite 217/0, RuboCop clean, Sorbet apm clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The round-26 change wired apm into `bin/dry-run.rb` (`require "dependabot/apm"`
plus the `apm/lib` load path), but the CLI also gates its first argument against
a `valid_package_managers` allowlist and exits with "Invalid package manager:
apm" before any work runs. apm was missing from that list, so the dry-run tool
could never actually exercise the apm ecosystem despite being required.
Add `apm` to the allowlist so `bin/dry-run.rb apm owner/repo` resolves the apm
file fetcher / parser / update checker like every other ecosystem.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ged deps
A merged apm dependency collapses to its lowest pin for both `version` and
`previous_version`, which broke two update paths when declarations span
different version lines:
- Security updates: when only a higher declaration is vulnerable (e.g. pins
`v1.0.0` and `v2.0.0` with an advisory on the latter), the lowest pin is
unaffected, so SecurityAdvisory#fixed_by? rejected the rewrite as
`security_update_not_possible`. updated_dependency_with_own_req_unlock now
surfaces the vulnerable pin as `previous_version` and its fix as `version`
so the security-update path recognises the merged update as a fix.
- Cooldown: GitCommitChecker#current_version came from the merged lowest pin,
so a `v2.1.0` candidate for a `v2.0.0` declaration was misclassified as a
major bump (1.0 -> 2.1) and held back. git_commit_checker_for now accepts a
`ref_version` to scope the checker's dependency version to each
declaration's own pin, so SemVer-distance cooldown classification reflects
that declaration.
Adds regression specs for both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Also adds the missing `L: apm` rule to `.github/labeler.yml`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7763b56b-55fc-4641-ac46-ba44225e75d3
APM packages have no GitHub Advisory Database coverage, so Dependabot
never supplies security advisories for them and the security-update flow
(gated on `vulnerable?`, which is false without advisories) never runs.
The security-specific logic in the update checker was therefore dead code.
Remove the `vulnerable?` override, the lowest-fix resolution
(`fetch_lowest_security_fix_version`, `lowest_security_fix_tag`,
`ref_vulnerable?`, `higher_than`), the per-line advisory handling in
`updated_dependency_with_own_req_unlock` / `resolved_tag_for_requirement`,
the now-unused `version_filters` require, and the matching specs.
Keep `lowest_security_fix_version` and `lowest_resolvable_security_fix_version`
as nil stubs since they are abstract in `UpdateCheckers::Base`; a comment notes
we have no visibility into whether APM security updates will be offered in the
future, which is where a real implementation would go.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a new
apmecosystem to Dependabot, adding support for bumping APM (Agent Package Manager) dependencies declared in anapm.ymlmanifest.APM is a git-based package manager for AI agent context — skills, prompts, chat modes, instructions and other agent primitives. Because every APM dependency resolves to a git ref, this ecosystem bumps versions the same way the GitHub Actions and git submodules ecosystems do: it resolves the newest semver tag on the remote via
Dependabot::GitCommitCheckerand rewrites the manifest ref.Refs #14350.
What it does
Given an
apm.ymllike:For each string-shorthand git dependency pinned to a semver tag, Dependabot:
https://<host>/<owner>/<repo>).GitCommitChecker).apm.yml(e.g.#v1.0.0→#v1.4.0), preserving the rest of the declaration byte-for-byte.devDependencies.apmentries are also updated and flagged non-production via thedevelopmentgroup.Scope of this first iteration (manifest-only)
To keep this PR small and reviewable, the following are intentionally out of scope — ignored, never modified, never erroring — and documented as follow-ups in
apm/README.md:git:/registry:/id:/path:maps) andmcp:entries — only the string shorthand is parsed../pkg,../pkg,/pkg).apm.lock.yamlis read (to report the package-manager version) but not rewritten; APM regenerates it after a manifest change.Implementation
Full ecosystem implementation, modeled on
github_actions(semver-tag resolution) blended withgit_submodules(git-based, cooldown):FileFetcher,FileParser,UpdateChecker,FileUpdater,MetadataFinderVersion,Requirement,PackageManager,PackageSpecifierNEW_ECOSYSTEMS.mdTesting
apmtype errors