Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 71 additions & 7 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ Complete guide to APM package dependency management - share and reuse context co

## What Are APM Dependencies?

APM dependencies are git repositories containing `.apm/` directories with context collections (instructions, chatmodes, contexts) and agent workflows (prompts). They enable teams to:
APM dependencies are reusable APM packages that APM can fetch either directly from git or indirectly through configured repositories such as an OCI registry. They enable teams to:

- **Share proven workflows** across projects and team members
- **Standardize compliance and design patterns** organization-wide
- **Build on tested context** instead of starting from scratch
- **Maintain consistency** across multiple repositories and teams

APM supports any git-accessible host — GitHub, GitLab, Bitbucket, self-hosted instances, and more.
APM supports direct git hosts such as GitHub, GitLab, Bitbucket, Azure DevOps, and self-hosted servers, plus repository-driven resolution for logical package requirements.

## Dependency Types

Expand Down Expand Up @@ -81,8 +81,13 @@ name: my-project
version: 1.0.0
dependencies:
apm:
# GitHub shorthand (default)
# Logical package requirements resolved through configured repositories
- microsoft/apm-sample-package#v1.0.0
- name: acme/security-pack
version: 1.2.0
repository: ghcr

# Logical requirement that will usually resolve from the default GitHub repository
- github/awesome-copilot/skills/review-and-refactor

# Full HTTPS git URL (any host)
Expand Down Expand Up @@ -116,10 +121,12 @@ dependencies:
KB_TOKEN: "${KB_TOKEN}"
```

APM accepts dependencies in two forms:
APM accepts dependencies in three forms:

**Logical requirement strings**:
- **Logical package requirement** (`owner/repo` or `owner/repo#v1.2.0`) — resolved through configured repositories

**String format** (simple cases):
- **Shorthand** (`owner/repo`) — defaults to GitHub
**Direct source strings**:
- **HTTPS URL** (`https://host/owner/repo.git`) — any git host, whole repo
- Custom port: `https://host:8443/owner/repo.git` — port is preserved in clone URLs
- **SSH URL** (`git@host:owner/repo.git`) — any git host, whole repo
Expand All @@ -130,11 +137,19 @@ APM accepts dependencies in two forms:
- For nested groups + virtual paths, use the object format below
- **Local path** (`./path`, `../path`, `/absolute/path`) — local filesystem package

**Object format** (when you need `path`, `ref`, or `alias` on a git URL):
**Object format**:
- **Logical requirement object** (`name` + optional `version` / `repository` / `alias`)
- **Git object** (`git` + optional `path` / `ref` / `alias`)
- **Local path object** (`path`)

```yaml
dependencies:
apm:
- name: acme/security-pack
version: 1.2.0
repository: corp-oci
alias: security-pack

- git: https://gitlab.com/acme/coding-standards.git
path: instructions/security # virtual sub-path inside the repo
ref: v2.0 # pin to a tag, branch, or commit
Expand Down Expand Up @@ -174,12 +189,61 @@ transitive host you want to allow.
> path: file.prompt.md
> ```

## Repository-Driven Resolution

Logical requirements keep package identity separate from transport. APM resolves them using repositories configured on the client machine in `~/.apm/repositories.yml`.

Built-in defaults:

| Name | Type | Base | Priority |
|------|------|------|----------|
| `github` | `git` | `https://github.com` | `100` |
| `gitlab` | `git` | `https://gitlab.com` | `90` |
| `ghcr` | `oci` | `ghcr.io/apm` | `80` |

Example override:

```yaml
repositories:
- name: corp-oci
type: oci
base: registry.example.com/apm
priority: 110

- name: github
type: git
base: https://github.com
priority: 100
```

Resolution rules:

1. If a dependency sets `repository: <name>`, APM only tries that configured repository.
2. Otherwise APM tries repositories in descending `priority` order.
3. The first repository that resolves and fetches the package wins.
4. The resolved transport details are recorded in `apm.lock.yaml`.

Bare `owner/repo` strings in `apm.yml` are treated as logical requirements. Use explicit git URLs or host-qualified refs such as `gitlab.com/group/repo` when you want to bypass repository resolution and point at a specific source.

## OCI-backed Packages

The current OCI prototype supports consuming raw APM packages from OCI registries through configured repositories.

Expected artifact shape:

- exactly one `*.tar.gz` file in the OCI artifact
- the archive contains raw APM package sources
- `apm.yml` is at the archive root or under one top-level directory

This prototype currently uses the `oras` CLI to pull OCI artifacts. Publishing OCI packages and media-type enforcement are not implemented yet.

### How Dependencies Are Stored (Canonical Format)

APM normalizes every dependency entry on write — no matter how you specify a package, the stored form in `apm.yml` is always a clean, canonical string. This works like Docker's default registry convention:

- **GitHub** is the default registry. The `github.com` host is stripped, leaving just `owner/repo`.
- **Non-default hosts** (GitLab, Bitbucket, self-hosted) keep their FQDN: `gitlab.com/owner/repo`.
- **Logical requirement objects** are preserved as objects because they carry repository-selection metadata.

| You type | Stored in apm.yml |
|----------|-------------------|
Expand Down
25 changes: 24 additions & 1 deletion docs/src/content/docs/guides/private-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar:
order: 9
---

A private APM package is just a private git repository with an `apm.yml`. There is no registry and no publish step — make the repo private, grant read access, and `apm install` handles the rest.
A private APM package can be consumed either from a private git repository or from a private OCI-backed repository configured in `~/.apm/repositories.yml`. In both cases, the package itself is still just an APM package with an `apm.yml`.

## Create the package

Expand Down Expand Up @@ -57,6 +57,29 @@ dependencies:

APM reuses the same port across protocols during clone fallback (so `ssh://host:7999/...` falls back to `https://host:7999/...`). If your host serves SSH and HTTPS on different ports and SSH is unreachable, pin the protocol that matches the port you need.

For private OCI-backed package storage, configure a repository on each client:

```yaml
# ~/.apm/repositories.yml
repositories:
- name: corp-oci
type: oci
base: registry.example.com/apm
priority: 100
```

Then reference the package logically in `apm.yml`:

```yaml
dependencies:
apm:
- name: your-org/my-private-package
version: 1.0.0
repository: corp-oci
```

Current OCI support is consume-only in this prototype. The OCI artifact is expected to contain one `*.tar.gz` with raw APM package sources.

## Share with your team

Every developer needs read access to the private repository and the appropriate token in their environment. For teams, a fine-grained PAT scoped to the organization works well — no write access required.
Expand Down
24 changes: 22 additions & 2 deletions docs/src/content/docs/reference/lockfile-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ dependencies:
package_type: apm_package
deployed_files:
- .github/instructions/common-guidelines.instructions.md

- repo_url: acme/security-pack
source_type: oci
repository_name: ghcr
oci_registry: ghcr
oci_repository: acme/security-pack
oci_tag: 1.2.0
resolved_ref: ghcr.io/apm/acme/security-pack:1.2.0
depth: 1
package_type: apm_package
deployed_files:
- .github/instructions/security.instructions.md
```

### 4.1 Top-Level Fields
Expand All @@ -113,8 +125,10 @@ fields:
|-------|------|----------|-------------|
| `repo_url` | string | MUST | Source repository URL, or `_local/<name>` for local path dependencies. |
| `host` | string | MAY | Git host identifier (e.g., `github.com`). Omitted when inferrable from `repo_url`. |
| `source_type` | string | MAY | Resolved transport type. Current prototype values: `git`, `oci`. |
| `repository_name` | string | MAY | Name of the configured repository that satisfied a logical requirement. |
| `resolved_commit` | string | MUST (remote) | Full 40-character commit SHA that was checked out. Required for remote (git) dependencies; MUST be omitted for local (`source: "local"`) dependencies. |
| `resolved_ref` | string | MUST (remote) | Git ref (tag, branch, SHA) that resolved to `resolved_commit`. Required for remote (git) dependencies; MUST be omitted for local (`source: "local"`) dependencies. |
| `resolved_ref` | string | MUST (remote) | Git ref (tag, branch, SHA) that resolved to `resolved_commit`, or the fully resolved OCI locator used for an OCI-backed dependency. Required for remote dependencies; MUST be omitted for local (`source: "local"`) dependencies. |
| `version` | string | MAY | Semantic version of the package, if declared in its manifest. |
| `virtual_path` | string | MAY | Sub-path within the repository for virtual (monorepo) packages. |
| `is_virtual` | boolean | MAY | `true` if the package is a virtual sub-package. Omitted when `false`. |
Expand All @@ -126,6 +140,10 @@ fields:
| `deployed_files` | array of strings | MUST | Every file path APM deployed for this dependency, relative to project root. |
| `source` | string | MAY | Dependency source. `"local"` for local path dependencies. Omitted for remote (git) dependencies. |
| `local_path` | string | MAY | Filesystem path (relative or absolute) to the local package. Present only when `source` is `"local"`. |
| `oci_registry` | string | MAY | Logical OCI repository name used during resolution. |
| `oci_repository` | string | MAY | OCI repository path that stored the package archive. |
| `oci_tag` | string | MAY | OCI tag used when pulling the artifact. |
| `oci_digest` | string | MAY | OCI digest, when available from the transport or lock refresh. |
| `is_insecure` | boolean | MAY | `true` when the dep was fetched over HTTP (unencrypted). Omitted when `false`. Presence forces re-approval on the next install: the apm.yml entry MUST carry `allow_insecure: true` and the invocation MUST pass `--allow-insecure` (or `--allow-insecure-host` for transitive deps). Absent or `false` means HTTPS/SSH. |
| `allow_insecure` | boolean | MAY | `true` when the user's manifest explicitly approved the HTTP fetch with `allow_insecure: true`. Persisted alongside `is_insecure` for replay safety: a legacy lockfile with `is_insecure: true` but no `allow_insecure` fail-closes to `allow_insecure: false`, forcing re-approval. Omitted when `false`. |

Expand All @@ -139,7 +157,9 @@ lists) SHOULD be omitted from the serialized output to keep the file concise.
Each dependency is uniquely identified by its `repo_url`, or by the
combination of `repo_url` and `virtual_path` for virtual packages.
For local path dependencies (`source: "local"`), the unique key is the
`local_path` value. A conforming lock file MUST NOT contain duplicate
`local_path` value. In the current OCI prototype, implementations MAY use an
OCI-specific internal key shape such as `oci:<registry>:<repository>` while
still storing the logical package identity in `repo_url`. A conforming lock file MUST NOT contain duplicate
entries for the same key.

### 4.4 Content Integrity
Expand Down
59 changes: 56 additions & 3 deletions docs/src/content/docs/reference/manifest-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ local_path_form = ("./" / "../" / "/" / "~/" / ".\\" / "..\\" / "~\\") path

| Segment | Required | Pattern | Description |
|---|---|---|---|
| `host` | OPTIONAL | FQDN (e.g. `gitlab.com`) | Git host. Defaults to `github.com`. |
| `host` | OPTIONAL | FQDN (e.g. `gitlab.com`) | Explicit host qualifier. When omitted, the string is treated as a logical package requirement resolved through configured repositories. When present, the string is treated as a direct host-qualified source. |
| `port` | OPTIONAL | `1`–`65535` | Non-default port on `ssh://`, `https://`, `http://` clone URLs. Not expressible in SCP shorthand. |
| `owner/repo` | REQUIRED | 2+ path segments of `[a-zA-Z0-9._-]+` | Repository path. GitHub uses exactly 2 segments (`owner/repo`). Non-GitHub hosts MAY use nested groups (e.g. `gitlab.com/group/sub/repo`). |
| `virtual_path` | OPTIONAL | Path segments after repo | Subdirectory, file, or collection within the repo. See §4.1.3. |
Expand All @@ -229,12 +229,12 @@ local_path_form = ("./" / "../" / "/" / "~/" / ".\\" / "..\\" / "~\\") path
```yaml
dependencies:
apm:
# GitHub shorthand (default host) — each line shows a syntax variant
# Logical package requirements
- microsoft/apm-sample-package # latest (lockfile pins commit SHA)
- microsoft/apm-sample-package#v1.0.0 # pinned to tag (immutable)
- microsoft/apm-sample-package#main # branch ref (may change over time)

# Non-GitHub hosts (FQDN preserved)
# Direct host-qualified sources (FQDN preserved)
- gitlab.com/acme/coding-standards
- bitbucket.org/team/repo#main

Expand Down Expand Up @@ -262,6 +262,29 @@ dependencies:

#### 4.1.2. Object Form

APM supports two object-style forms for `dependencies.apm` entries:

- a **logical requirement object**, resolved through configured repositories
- a **direct git/local object**, which points at an explicit source

Logical requirement object:

| Field | Type | Required | Pattern / Constraint | Description |
|---|---|---|---|---|
| `name` | `string` | REQUIRED | package name with at least one `/` | Logical package identity, for example `acme/security-pack`. |
| `version` | `string` | OPTIONAL | non-empty string | Version or ref-style selector used during resolution. |
| `repository` | `string` | OPTIONAL | non-empty string | Name of a configured repository in `~/.apm/repositories.yml`. |
| `alias` | `string` | OPTIONAL | `^[a-zA-Z0-9._-]+$` | Local display alias. |

```yaml
- name: acme/security-pack
version: 1.2.0
repository: corp-oci
alias: security-pack
```

Direct git/local object:

REQUIRED when the shorthand is ambiguous (e.g. nested-group repos with virtual paths).

| Field | Type | Required | Pattern / Constraint | Description |
Expand All @@ -286,6 +309,36 @@ Local path dependency (development only):
- path: ./packages/my-shared-skills
```

#### 4.1.2.1. Repository Configuration

Logical requirement resolution is driven by a client-side file at `~/.apm/repositories.yml`.

```yaml
repositories:
- name: github
type: git
base: https://github.com
priority: 100

- name: ghcr
type: oci
base: ghcr.io/apm
priority: 80
```

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | REQUIRED | Stable repository identifier used by `dependencies.apm[*].repository`. |
| `type` | `string` | REQUIRED | Repository backend type. Current values: `git`, `oci`. |
| `base` | `string` | REQUIRED | Base locator prefix for the repository. |
| `priority` | `integer` | OPTIONAL | Higher values are tried first when a dependency does not pin `repository`. |

Built-in defaults are used when the file is absent or invalid:

- `github` → `git` → `https://github.com` → priority `100`
- `gitlab` → `git` → `https://gitlab.com` → priority `90`
- `ghcr` → `oci` → `ghcr.io/apm` → priority `80`

#### 4.1.3. Virtual Packages

A dependency MAY target a subdirectory, file, or collection within a repository rather than the whole repo. Conforming resolvers MUST classify virtual packages using the following rules, evaluated in order:
Expand Down
Loading