Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ devDependencies:

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.

**Important:** plain `apm install` (no flag) deploys both `dependencies` and `devDependencies` -- there is currently no `--omit=dev` flag. The dev/prod separation kicks in at `apm pack --format plugin`. Maintainer-only primitives that you author yourself MUST live outside `.apm/` to be excluded from plugin bundles, because the local-content scanner operates on `.apm/` regardless of the devDep marker. See [Dev-only Primitives](../dev-only-primitives/) for the canonical pattern.

## Local Path Dependencies

Install packages from the local filesystem for fast iteration during development.
Expand Down
82 changes: 82 additions & 0 deletions docs/src/content/docs/guides/dev-only-primitives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: "Dev-only Primitives"
description: "Author maintainer-only skills, agents, and instructions that stay out of shipped artifacts."
sidebar:
order: 7
---

Some primitives are useful while you author an APM package but should never reach consumers: a release-checklist skill for maintainers, a debugging agent that only makes sense in your repo, instructions that reference internal infrastructure. APM has one canonical pattern for this. Once you know it, three otherwise surprising behaviors stop being surprising.

## The pattern

```
your-package/
+-- apm.yml
+-- .apm/ # shipped source root
| +-- skills/
| | +-- public-skill/SKILL.md
| +-- agents/
+-- dev/ # maintainer-only, outside .apm/
+-- skills/
+-- release-checklist/SKILL.md
```

```yaml
# apm.yml
name: your-package
version: 1.0.0

dependencies:
apm:
- microsoft/apm-sample-package#v1.0.0

devDependencies:
apm:
- path: ./dev/skills/release-checklist
```

`apm install --dev` deploys the release-checklist skill to `.github/skills/release-checklist/` with `is_dev: true` in the lockfile. `apm pack --format plugin` excludes it. Consumers running `apm install your-org/your-package` never see it.

## Why outside `.apm/`?

APM treats `.apm/` as the publishable source root. The local-content scanner that builds plugin bundles operates on `.apm/` only, and it does NOT consult the devDependency marker when deciding what to include. If a dev-only skill sits under `.apm/skills/`, it ships -- even if the only reference to it in `apm.yml` is under `devDependencies`.

Authoring dev-only primitives anywhere outside `.apm/` (`dev/`, `internal/`, `.maintainer/` -- your choice) keeps them invisible to the scanner. Referencing them via local-path `devDependencies` keeps them installable on `apm install --dev` and tracked in the lockfile.

## Three behaviors this pattern works around

1. **The scanner does not honor the devDep marker.** It scans `.apm/` wholesale at pack time. The cure is to live outside `.apm/`.

2. **`includes:` is allow-list only.** There is no `exclude:` form. You cannot write `includes: [.apm/]` and expect a sibling `.apm/dev/` subtree to be stripped -- `includes` does not gate `.apm/` against itself, and the manifest schema has no exclude verb. See [Manifest section 3.9](../../reference/manifest-schema/#39-includes).

3. **Plain `apm install` deploys devDeps.** `apm install` (no flag) resolves and deploys both `dependencies` and `devDependencies`. `apm install --dev <pkg>` adds a new dev dependency to the manifest -- it is not a filter that excludes prod deps. There is currently no `--omit=dev` flag; the dev/prod separation kicks in at `apm pack --format plugin` time, not at install time.

## When to use this pattern

- Maintainer-only release tooling (changelog drafters, version-bump helpers).
- Internal debugging agents you do not want consumers to load.
- Test-fixture skills referenced by your own CI but not by consumers.
- Anything that would embarrass you if it shipped.

## When NOT to use this pattern

- Shared dev tooling that another package consumes -- that is a regular remote `devDependencies` entry (`owner/test-helpers`), not a local path.
- A primitive a consumer might legitimately want -- keep it under `.apm/`.

## Verifying

```bash
apm install --dev # deploys with is_dev: true
grep is_dev apm.lock.yaml # confirm marker
apm pack --format plugin --dry-run # confirm absence from bundle
ls build/your-package-1.0.0/skills/ # release-checklist must NOT appear
```

If the dev-only skill appears in the dry-run output, it is sitting under `.apm/`. Move it to `dev/` (or any non-`.apm/` path) and re-reference it via `path:` in `devDependencies`.

## See also

- [Anatomy of an APM Package](../../introduction/anatomy-of-an-apm-package/) -- why `.apm/` is the publishable source root.
- [Manifest Schema 3.9 -- `includes`](../../reference/manifest-schema/#39-includes) -- allow-list semantics, no exclude form.
- [Manifest Schema 5 -- `devDependencies`](../../reference/manifest-schema/#5-devdependencies) -- the field reference.
- [Pack & Distribute -- Plugin format](./pack-distribute/#plugin-format) -- what the scanner emits.
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link uses ./pack-distribute/#plugin-format, but other pages in guides consistently link to sibling pages with ../<page>/... (because routes are /guides/<page>/). As written, it will resolve under /guides/dev-only-primitives/pack-distribute/ in the built site. Suggest changing it to ../pack-distribute/#plugin-format.

Suggested change
- [Pack & Distribute -- Plugin format](./pack-distribute/#plugin-format) -- what the scanner emits.
- [Pack & Distribute -- Plugin format](../pack-distribute/#plugin-format) -- what the scanner emits.

Copilot uses AI. Check for mistakes.
4 changes: 3 additions & 1 deletion docs/src/content/docs/guides/pack-distribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ Dependencies listed under [`devDependencies`](../../reference/manifest-schema/#5
apm install --dev owner/test-helpers
```

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

**Caveat for primitives you author yourself:** the dev/prod split is enforced via the lockfile's `is_dev` marker for resolved dependencies. The local-content scanner that ships your own `.apm/` content does NOT consult that marker -- it bundles everything under `.apm/`. To keep maintainer-only primitives (release-checklist skills, internal debugging agents) out of plugin bundles, author them OUTSIDE `.apm/` (e.g. under `dev/`) and reference them via a local-path devDependency. See [Dev-only Primitives](./dev-only-primitives/).
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intra-guides links elsewhere in the docs use the ../<page>/ form (e.g. guides/dependencies.md -> ../pack-distribute/). Using ./dev-only-primitives/ here will resolve under the current page path in the built site (e.g. /guides/pack-distribute/dev-only-primitives/), which breaks navigation. Recommend switching this to ../dev-only-primitives/ for consistency and correct routing.

Suggested change
**Caveat for primitives you author yourself:** the dev/prod split is enforced via the lockfile's `is_dev` marker for resolved dependencies. The local-content scanner that ships your own `.apm/` content does NOT consult that marker -- it bundles everything under `.apm/`. To keep maintainer-only primitives (release-checklist skills, internal debugging agents) out of plugin bundles, author them OUTSIDE `.apm/` (e.g. under `dev/`) and reference them via a local-path devDependency. See [Dev-only Primitives](./dev-only-primitives/).
**Caveat for primitives you author yourself:** the dev/prod split is enforced via the lockfile's `is_dev` marker for resolved dependencies. The local-content scanner that ships your own `.apm/` content does NOT consult that marker -- it bundles everything under `.apm/`. To keep maintainer-only primitives (release-checklist skills, internal debugging agents) out of plugin bundles, author them OUTSIDE `.apm/` (e.g. under `dev/`) and reference them via a local-path devDependency. See [Dev-only Primitives](../dev-only-primitives/).

Copilot uses AI. Check for mistakes.

### Example output

Expand Down
19 changes: 19 additions & 0 deletions docs/src/content/docs/guides/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,25 @@ my-package/

On install, APM promotes each sub-skill to a top-level `.github/skills/` entry alongside the parent — see [Sub-skill Promotion](#sub-skill-promotion) below.

### Option 5: Maintainer-only Skill (Dev-only)

For skills you want during authoring but not shipped to consumers (release-checklist skills, internal debugging skills), author them OUTSIDE `.apm/` and reference them via a local-path devDependency:

```
your-package/
+-- apm.yml
+-- .apm/skills/... # public skills
+-- dev/skills/release-checklist/SKILL.md # maintainer-only
```

```yaml
devDependencies:
apm:
- path: ./dev/skills/release-checklist
```

`apm install --dev` deploys the skill locally; `apm pack --format plugin` excludes it. See [Dev-only Primitives](../dev-only-primitives/) for the full pattern.

### Sub-skill Promotion

When a package contains sub-skills in `.apm/skills/*/` subdirectories, APM promotes each to a top-level entry under `.github/skills/`. This ensures Copilot can discover them independently, since it only scans direct children of `.github/skills/`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,13 @@ next `apm install`?** Your edit gets overwritten. Edit the source under
**I ran `ls` and don't see `.apm/`.** It's a dotfile directory, hidden by
default. Use `ls -a`.

**I have a skill I want for development but not shipped to consumers.
Where does it go?** Outside `.apm/`. The local-content scanner that builds
plugin bundles operates on `.apm/` only and does not consult the
devDependency marker. Author dev-only primitives under `dev/` (or any
non-`.apm/` path) and reference them via a local-path devDependency. See
[Dev-only Primitives](../../guides/dev-only-primitives/).

**Do I need `.apm/` to install packages?** No. `.apm/` is for authoring. If
you only consume packages, `apm install` creates the runtime targets
(`.github/`, `.claude/`, etc.) directly under `apm_modules/` and you never
Expand Down
12 changes: 12 additions & 0 deletions docs/src/content/docs/reference/manifest-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ includes: auto
# - .apm/skills/my-skill/
```

**`includes:` is allow-list only.** There is no `exclude:` form. The field controls which `.apm/` content the project consents to publish; it cannot be used to fence off subdirectories of `.apm/` from the scanner. To keep maintainer-only primitives out of shipped artifacts, author them OUTSIDE `.apm/` and reference them via a local-path devDependency -- see [Dev-only Primitives](../../guides/dev-only-primitives/).

When `policy.manifest.require_explicit_includes` is `true` (see [Governance guide](../../enterprise/governance-guide/)), only form 3 passes the policy check; `auto` and undeclared are rejected at install/audit time by the `explicit-includes` policy check (not at YAML parse time).

### 3.10. `policy`
Expand Down Expand Up @@ -445,6 +447,16 @@ Created automatically by `apm init --plugin`. Use [`apm install --dev`](../cli-c
apm install --dev owner/test-helpers
```

Plain `apm install` (no flag) deploys both `dependencies` and `devDependencies`. There is currently no `--omit=dev` flag -- the dev/prod separation kicks in at `apm pack --format plugin` time. The local-content scanner that builds plugin bundles also operates on `.apm/` only and does not consult the devDep marker. To keep maintainer-only primitives out of shipped artifacts, author them outside `.apm/` and reference them via a local-path devDependency. See [Dev-only Primitives](../../guides/dev-only-primitives/).

Local-path devDependency example:

```yaml
devDependencies:
apm:
- path: ./dev/skills/release-checklist
```

---

## 6. Compilation
Expand Down
Loading