Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
85a4e59
feat: implement plugin exporter and apm pack --format plugin
danielmeppiel Mar 19, 2026
a0521cb
feat: add apm init --plugin and devDependencies model
danielmeppiel Mar 19, 2026
a100ff5
docs: update documentation for plugin coexistence features
danielmeppiel Mar 19, 2026
ca6e6de
fix: address security and DX review findings in plugin exporter
danielmeppiel Mar 20, 2026
18ef844
docs: add plugin coexistence entries to changelog
danielmeppiel Mar 20, 2026
a882a82
feat: add SHA-256 content integrity hashing in lockfile (#315)
danielmeppiel Mar 20, 2026
d5663b2
feat: resolver devDependencies awareness and is_dev tracking
danielmeppiel Mar 20, 2026
50bab52
fix: eliminate TOCTOU in content hash verification
danielmeppiel Mar 20, 2026
fdc6aa0
docs: update changelog with install --dev and content hashing entries
danielmeppiel Mar 20, 2026
79d8151
docs: document install --dev flag and content integrity hashing
danielmeppiel Mar 20, 2026
90f6a48
fix: remove nonexistent context/memory primitives from plugin export
danielmeppiel Mar 20, 2026
cda3978
fix: address PR review — path traversal, symlink guard, error messaging
danielmeppiel Mar 20, 2026
1ae08ed
refactor: use path_security utils instead of ad-hoc traversal checks
danielmeppiel Mar 20, 2026
a980c9f
fix: address round-2 PR review — dead code, local hash skip, dev filt…
danielmeppiel Mar 20, 2026
7b423ec
docs: comprehensive documentation audit fixes (15 files)
danielmeppiel Mar 20, 2026
716943b
fix: devDep filtering uses composite key + local plugin.json install
danielmeppiel Mar 20, 2026
f70e49f
fix: bare skill export, local plugin normalization, remove context/me…
danielmeppiel Mar 20, 2026
e9fd8ca
refactor: centralize package type detection into detect_package_type()
danielmeppiel Mar 20, 2026
09278f4
Merge branch 'main' into feature/plugin-coexistence
danielmeppiel Mar 20, 2026
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
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `apm pack --format plugin` — export APM packages as standalone plugin directories consumable by Copilot CLI, Claude Code, and other plugin hosts. Transforms `.apm/` layout to plugin-native directories (agents, skills, commands, instructions, contexts) with hooks/MCP merging, collision handling, and security scanning (#378)
- `apm init --plugin` — initialize a plugin authoring project with both `plugin.json` and `apm.yml` (includes `devDependencies` section). Validates kebab-case plugin names per plugin spec (#378)
- `devDependencies` support in `apm.yml` and `APMPackage` model — same syntax as `dependencies`, parsed with `get_dev_apm_dependencies()`/`get_dev_mcp_dependencies()` accessors. Dev deps are excluded from plugin bundles (#378)
- `apm install --dev` — install packages as development dependencies, writing to `devDependencies` instead of `dependencies` (#378)
- `apm pack --force` flag — on collision, last writer wins instead of first (#378)
- `synthesize_plugin_json_from_apm_yml()` — generates `plugin.json` from `apm.yml` identity fields when no plugin manifest exists (#378)

### Security

- Content integrity hashing — SHA-256 checksums of package file trees are stored in `apm.lock.yaml` (`content_hash` field) and verified on subsequent installs. Detects tampering, MITM modifications, or force-pushed commits (#315, #378)
- Lockfile `is_dev` tracking — dev dependencies are explicitly marked in the lockfile for auditability (#378)
### Changed

- Install URLs now use short `aka.ms/apm-unix` and `aka.ms/apm-windows` redirects across README, docs, CLI output, and install script
Expand Down Expand Up @@ -250,7 +263,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **SKILL.md Parsing**: Parse SKILL.md directly without requiring apm.yml generation
- **Git Host Errors**: Actionable error messages for unsupported Git hosts

## [0.7.0] - 2025-12-19
## [0.7.0] - 2024-12-19

### Changed

Expand Down
Binary file added copilot-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added copilot-cli-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions docs/src/content/docs/contributing/integration-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ pytest tests/integration/test_runtime_smoke.py::TestRuntimeSmoke::test_codex_run

#### Option 1: Complete CI Process Simulation (Recommended)
```bash
```bash
export GITHUB_TOKEN=your_token_here
./scripts/test-integration.sh
```
```

This script (`scripts/test-integration.sh`) is a unified script that automatically adapts to your environment:

Expand Down
39 changes: 33 additions & 6 deletions docs/src/content/docs/enterprise/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The `resolved_commit` field is a full 40-character SHA, not a branch name or tag

### No registry

APM does not use a package registry. Dependencies are specified as git repository URLs in `apm.yaml`. This eliminates the registry compromise vector entirely — there is no centralized service that can be poisoned to redirect installs.
APM does not use a package registry. Dependencies are specified as git repository URLs in `apm.yml`. This eliminates the registry compromise vector entirely — there is no centralized service that can be poisoned to redirect installs.

## Content scanning

Expand Down Expand Up @@ -139,9 +139,26 @@ Content scanning detects hidden Unicode characters. It does not detect:

### Planned hardening

- **Content integrity hashing** — SHA-256 checksums stored in `apm.lock.yaml` to verify downloaded content hasn't been tampered with.
- **Hook transparency** — display hook script contents during install so developers can review what will execute.

## Content integrity hashing

APM computes a SHA-256 hash of each downloaded package's file tree and stores it in `apm.lock.yaml` as `content_hash`. On subsequent installs, cached packages are verified against the lockfile hash. A mismatch triggers a warning and re-download.

```yaml
# apm.lock.yaml
dependencies:
- repo_url: https://github.com/acme-corp/security-baseline
resolved_commit: a1b2c3d4e5f6...
content_hash: "sha256:9f86d081884c7d659a2feaa0c55ad015..."
```

The hash is deterministic — computed over sorted file paths and contents, independent of filesystem metadata (timestamps, permissions). `.git/` and `__pycache__/` directories are excluded.

Lock files generated before this feature omit `content_hash`. APM handles this gracefully — verification is skipped and the hash is populated on the next install.

See the [Lock File Specification](../../reference/lockfile-spec/#44-content-integrity) for field details.

## Path security

APM deploys files only to controlled subdirectories within the project root.
Expand All @@ -163,6 +180,7 @@ Symlinks are never followed during artifact operations:
- **Tree copy operations** skip symlinks entirely — they are excluded from the copy via an ignore filter.
- **MCP configuration files** that are symlinks are rejected with a warning and not parsed.
- **Manifest parsing** requires files to pass both `.is_file()` and `not .is_symlink()` checks.
- **Archive creation** — `apm pack` excludes symlinks from bundled archives. Packaged artifacts contain no symbolic links, preventing symlink-based escape attacks in distributed bundles.

This prevents symlink-based attacks that could escape allowed directories or cause APM to read or write outside the project root.

Expand All @@ -174,21 +192,30 @@ When APM deploys a file, it checks whether a file already exists at the target p
- If the file is **not tracked** (user-authored or created by another tool), APM skips it and prints a warning.
- The `--force` flag overrides collision detection, allowing APM to overwrite untracked files.

### Development dependency isolation

APM separates production and development dependencies:

- **Production dependencies** (`dependencies.apm`) are included in plugin bundles and shared packages.
- **Development dependencies** (`devDependencies.apm`, installed via `apm install --dev`) are resolved and cached locally but **excluded** from `apm pack --format plugin` output.

This prevents transitive inclusion of development-only packages (test fixtures, linting rules, internal helpers) in distributed artifacts. The lockfile marks dev dependencies with `is_dev: true` for explicit tracking. See the [Lock File Specification](../../reference/lockfile-spec/#42-dependency-entries) for field details.

## MCP server trust model

APM integrates MCP (Model Context Protocol) server configurations from packages. Trust is explicit and scoped by dependency depth.

### Direct dependencies

MCP servers declared by your direct dependencies (packages listed in your `apm.yaml`) are auto-trusted. You explicitly chose to depend on these packages, so their MCP server declarations are accepted.
MCP servers declared by your direct dependencies (packages listed in your `apm.yml`) are auto-trusted. You explicitly chose to depend on these packages, so their MCP server declarations are accepted.

### Transitive dependencies

MCP servers declared by transitive dependencies (dependencies of your dependencies) are **blocked by default**. Transitive MCP servers can request tool access, file system permissions, or network capabilities — blocking them ensures that adding a prompt package cannot silently grant MCP access to an unknown transitive dependency.

To allow transitive MCP servers, you must either:

- **Re-declare the dependency** in your own `apm.yaml`, promoting it to a direct dependency.
- **Re-declare the dependency** in your own `apm.yml`, promoting it to a direct dependency.
- **Pass `--trust-transitive-mcp`** to explicitly opt in to transitive MCP servers for that install.

## Token handling
Expand All @@ -200,7 +227,7 @@ APM authenticates to git hosts using personal access tokens (PATs) read from env
| GitHub packages | `GITHUB_APM_PAT`, `GITHUB_TOKEN`, `GH_TOKEN` |
| Azure DevOps packages | `ADO_APM_PAT` |

- **Never stored in files.** Tokens are read from the environment at runtime. They are never written to `apm.yaml`, `apm.lock.yaml`, or any generated file.
- **Never stored in files.** Tokens are read from the environment at runtime. They are never written to `apm.yml`, `apm.lock.yaml`, or any generated file.
- **Never logged.** Token values are not included in console output, error messages, or debug logs.
- **Scoped to their git host.** A GitHub token is only sent to GitHub. An Azure DevOps token is only sent to Azure DevOps. Tokens are never transmitted to any other endpoint.

Expand All @@ -211,7 +238,7 @@ For GitHub, a fine-grained PAT with read-only `Contents` permission on the repos
| Vector | Traditional package manager | APM |
|---|---|---|
| Registry compromise | Attacker poisons central registry | No registry exists |
| Version substitution | Malicious version replaces legitimate one | Lock file pins exact commit SHA |
| Version substitution | Malicious version replaces legitimate one | Lock file pins exact commit SHA; content hash detects post-download tampering |
| Post-install scripts | Arbitrary code runs after install | No code execution |
| Typosquatting | Similar package names on registry | Dependencies are full git URLs |
| Build-time injection | Malicious build steps execute | No build step — files are copied |
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/getting-started/first-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,5 @@ This produces `AGENTS.md` (for Codex, Gemini) and `CLAUDE.md` for tools that nee

- Add [skills](/apm/guides/skills/) to your package
- Set up [dependencies](/apm/guides/dependencies/) on other packages
- Distribute as a standalone plugin — see [Plugin authoring](../../guides/plugins/#plugin-authoring) and [Pack & Distribute](../../guides/pack-distribute/)
- Explore the [CLI reference](/apm/reference/cli-commands/) for more commands
2 changes: 1 addition & 1 deletion docs/src/content/docs/getting-started/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ No uninstall script, no cleanup command. Zero risk.

## Next steps

- [Quick start](../quickstart/) — first-time setup walkthrough
- [Quick start](../quick-start/) — first-time setup walkthrough
- [Dependencies](../../guides/dependencies/) — managing external packages
- [Manifest schema](../../reference/manifest-schema/) — full `apm.yml` reference
- [CLI commands](../../reference/cli-commands/) — complete command reference
25 changes: 22 additions & 3 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ APM supports multiple dependency types:
|------|-----------|---------|
| **APM Package** | Has `apm.yml` | `microsoft/apm-sample-package` |
| **Marketplace Plugin** | Has `plugin.json` (no `apm.yml`) | `github/awesome-copilot/plugins/context-engineering` |
| **Claude Skill** | Has `SKILL.md` (no `apm.yml`) | `ComposioHQ/awesome-claude-skills/brand-guidelines` || **Hook Package** | Has `hooks/*.json` (no `apm.yml` or `SKILL.md`) | `anthropics/claude-plugins-official/plugins/hookify` || **Virtual Subdirectory Package** | Folder path in monorepo | `ComposioHQ/awesome-claude-skills/mcp-builder` |
| **Claude Skill** | Has `SKILL.md` (no `apm.yml`) | `ComposioHQ/awesome-claude-skills/brand-guidelines` |
| **Hook Package** | Has `hooks/*.json` (no `apm.yml` or `SKILL.md`) | `anthropics/claude-plugins-official/plugins/hookify` |
| **Virtual Subdirectory Package** | Folder path in monorepo | `ComposioHQ/awesome-claude-skills/mcp-builder` |
| **Virtual Subdirectory Package** | Folder path in repo | `github/awesome-copilot/skills/review-and-refactor` |
| **Local Path Package** | Path starts with `./`, `../`, or `/` | `./packages/my-shared-skills` |
| **ADO Package** | Azure DevOps repo | `dev.azure.com/org/project/_git/repo` |
Expand Down Expand Up @@ -221,11 +223,28 @@ apm deps info apm-sample-package
# Compile with dependencies
apm compile

# The compilation process generates distributed AGENTS.md files across the project
# Compilation generates distributed files across the project
# Instructions with matching applyTo patterns are merged from all sources
# See docs/wip/distributed-agents-compilation-strategy.md for detailed compilation logic
```

## Development Dependencies

Some packages are only needed during authoring — test fixtures, linting rules, internal helpers. Install them as dev dependencies so they stay out of distributed bundles:

```bash
apm install --dev owner/test-helpers
```

Or declare them directly:

```yaml
devDependencies:
apm:
- source: owner/test-helpers
```

Dev dependencies install to `apm_modules/` like production deps but are excluded from `apm pack --format plugin` output. See [Pack & Distribute](../pack-distribute/) for details.

## Local Path Dependencies

Install packages from the local filesystem for fast iteration during development.
Expand Down
60 changes: 60 additions & 0 deletions docs/src/content/docs/guides/pack-distribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ apm pack --dry-run
| `--archive` | off | Produce `.tar.gz` instead of directory |
| `-o, --output` | `./build` | Output directory |
| `--dry-run` | off | List files without writing |
| `--force` | off | On collision (plugin format), last writer wins |

### Target filtering

Expand Down Expand Up @@ -145,6 +146,65 @@ build/my-project-1.0.0/

The bundle is self-describing: its `apm.lock.yaml` lists every file it contains and the dependency graph that produced them.

## Plugin format

`apm pack --format plugin` transforms your project into a standalone plugin directory consumable by Copilot CLI, Claude Code, or other plugin hosts. The output contains no APM-specific files — no `apm.yml`, `apm_modules/`, `.apm/`, or `apm.lock.yaml`.

Use this when you want to distribute your APM package as a standalone plugin that works without APM.

```bash
apm pack --format plugin
```

### Output mapping

The exporter remaps `.apm/` content into plugin-native paths:

| APM source | Plugin output |
|---|---|
| `.apm/agents/*.agent.md` | `agents/*.agent.md` |
| `.apm/skills/*/SKILL.md` | `skills/*/SKILL.md` |
| `.apm/prompts/*.prompt.md` | `commands/*.md` |
| `.apm/prompts/*.md` | `commands/*.md` |
| `.apm/instructions/*.instructions.md` | `instructions/*.instructions.md` |
| `.apm/hooks/*.json` | `hooks.json` (merged) |
| `.apm/commands/*.md` | `commands/*.md` |

Prompt files are renamed: `review.prompt.md` becomes `review.md` in `commands/`.

**Excluded from plugin output:** `devDependencies` are excluded from plugin bundles — see [devDependencies](../../reference/manifest-schema/#5-devdependencies).

### plugin.json generation

The bundle includes a `plugin.json`. If one already exists in the project (at the root, `.github/plugin/`, `.claude-plugin/`, or `.cursor-plugin/`), it is used and updated with component paths reflecting the output layout. Otherwise, APM synthesizes one from `apm.yml` metadata.

### devDependencies exclusion

Dependencies listed under [`devDependencies`](../../reference/manifest-schema/#5-devdependencies) in `apm.yml` are excluded from the plugin bundle. Use [`apm install --dev`](../../reference/cli-commands/#apm-install---install-apm-and-mcp-dependencies) to add dev deps:

```bash
apm install --dev owner/test-helpers
```

This keeps development-only packages (test helpers, lint rules) out of distributed plugins.

### Example output

```
build/my-plugin-1.0.0/
agents/
architect.agent.md
skills/
security-scan/
SKILL.md
commands/
review.md
instructions/
coding-standards.instructions.md
hooks.json
plugin.json
```

## Lockfile enrichment

The bundle includes a copy of `apm.lock.yaml` enriched with a `pack:` section. The project's own `apm.lock.yaml` is never modified.
Expand Down
31 changes: 31 additions & 0 deletions docs/src/content/docs/guides/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ sidebar:

APM supports plugins through the `plugin.json` format. Plugins are automatically detected and integrated into your project as standard APM dependencies.

## Plugin authoring

Plugin ecosystems handle distribution but lack dependency management, security scanning, version locking, and dev/prod separation. As plugins depend on shared primitives, these gaps compound.

APM is the supply-chain layer. Author packages with full tooling — transitive dependencies, lockfile pinning, [security scanning](../../enterprise/security/), [`devDependencies`](../../reference/manifest-schema/#5-devdependencies) — then export as standard plugins. Consumers never need APM installed.

### Three modes

| Mode | Manifests | Use when |
|------|-----------|----------|
| **APM-only** | `apm.yml` | Full APM workflow — deploy to `.github/`, `.claude/`, `.cursor/`, `.opencode/` |
| **Plugin-only** | `plugin.json` | Standard plugin consumed by APM via `apm install` — metadata synthesized automatically |
| **Hybrid** | `apm.yml` + `plugin.json` | Author with dependency management + security, export as standalone plugins |

**APM-only** is the default for teams using APM end-to-end. **Plugin-only** requires no changes to existing plugins — APM consumes them as-is. **Hybrid** is for plugin authors who want APM's supply-chain features during development while distributing standard plugins.

### Hybrid authoring workflow

```bash
apm init my-plugin --plugin # Creates both apm.yml and plugin.json
apm install --dev owner/helpers # Dev-only dependency (excluded from export)
apm install owner/core-rules # Production dependency
apm pack --format plugin # Export — dev deps excluded, security scanned
```

The exported plugin directory contains no APM-specific files. See [Pack & Distribute — Plugin format](../../guides/pack-distribute/#plugin-format) for the output mapping.

## Overview

Plugins are packages that contain:
Expand Down Expand Up @@ -306,6 +333,10 @@ This:
- Integrates skills into the runtime
- Includes prompt primitives

## Exporting APM packages as plugins

Use the [hybrid authoring workflow](#hybrid-authoring-workflow) to develop plugins with APM's full tooling and export them as standalone plugin directories. See [Pack & Distribute — Plugin format](../../guides/pack-distribute/#plugin-format) for the output mapping and structure.

## Finding Plugins

Plugins can be found through:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/guides/private-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Private Packages"
description: "Create and distribute private APM packages within your team or organization."
sidebar:
order: 8
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.
Expand Down
11 changes: 11 additions & 0 deletions docs/src/content/docs/integrations/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ Use `apm pack` in CI to build a distributable bundle once, then consume it in do
path: build/*.tar.gz
```

### Pack as standalone plugin

```yaml
# Export as standalone plugin
- run: apm pack --format plugin
- uses: actions/upload-artifact@v4
with:
name: plugin-bundle
path: build/*.tar.gz
```

### Consume in another job (no APM needed)

```yaml
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/integrations/gh-aw.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The APM compilation target is automatically inferred from the configured `engine

### apm-action Pre-Step

For more control over the installation process, use [`microsoft/apm-action@v1`](https://github.com/microsoft/apm-action) as an explicit workflow step. This approach runs `apm install && apm compile` directly, giving you access to the full APM CLI.
For more control over the installation process, use [`microsoft/apm-action@v1`](https://github.com/microsoft/apm-action) as an explicit workflow step. This approach runs `apm install` directly, giving you access to the full APM CLI. To also compile, add `compile: true` to the action configuration.

```yaml
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ APM provides first-class support for MCP servers, including registry-based serve
APM auto-discovers MCP server declarations from packages during `apm install`:

- **apm.yml dependencies**: MCP servers listed under `dependencies.mcp` in a package's `apm.yml` are collected automatically.
- **plugin.json**: Packages with a `plugin.json` (at the root, `.github/plugin/`, or `.claude-plugin/`) are recognized as marketplace plugins. APM synthesizes an `apm.yml` from `plugin.json` metadata when no `apm.yml` exists.
- **plugin.json**: Packages with a `plugin.json` (at the root, `.github/plugin/`, or `.claude-plugin/`) are recognized as marketplace plugins. APM synthesizes an `apm.yml` from `plugin.json` metadata when no `apm.yml` exists. When both files are present (hybrid mode), APM uses `apm.yml` for dependency management while preserving `plugin.json` for plugin ecosystem compatibility. See [Plugin authoring](../../guides/plugins/#plugin-authoring).
- **Transitive collection**: APM walks the dependency tree and collects MCP servers from all transitive packages.

### Trust Model
Expand Down
Loading
Loading