diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index f0790d96..364c7f18 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -50,6 +50,7 @@ export default defineConfig({ { label: 'Why APM?', slug: 'introduction/why-apm' }, { label: 'How It Works', slug: 'introduction/how-it-works' }, { label: 'Key Concepts', slug: 'introduction/key-concepts' }, + { label: 'Anatomy of an APM Package', slug: 'introduction/anatomy-of-an-apm-package' }, ], }, { diff --git a/docs/src/content/docs/getting-started/first-package.md b/docs/src/content/docs/getting-started/first-package.md index dae28696..2c81521c 100644 --- a/docs/src/content/docs/getting-started/first-package.md +++ b/docs/src/content/docs/getting-started/first-package.md @@ -26,7 +26,7 @@ my-coding-standards/ └── apm.yml # Package manifest ``` -> **Note:** By default, `apm init` creates only `apm.yml`. The directory structure below is what you build manually in the following steps. +> **Note:** By default, `apm init` creates only `apm.yml`. The directory structure below is what you build manually in the following steps. See [Anatomy of an APM Package](../../introduction/anatomy-of-an-apm-package/) for what `.apm/` is and why files live there. ## 2. Add an Instruction diff --git a/docs/src/content/docs/guides/plugins.md b/docs/src/content/docs/guides/plugins.md index b3dbf3c5..e5e1aaf5 100644 --- a/docs/src/content/docs/guides/plugins.md +++ b/docs/src/content/docs/guides/plugins.md @@ -8,6 +8,8 @@ APM supports plugins through the `plugin.json` format. Plugins are automatically ## Plugin authoring +For where plugins fit relative to APM's source layout, see [Anatomy -- Why not just ship a `plugin.json`?](../../introduction/anatomy-of-an-apm-package/#why-not-just-ship-a-pluginjson). + 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. diff --git a/docs/src/content/docs/integrations/gh-aw.md b/docs/src/content/docs/integrations/gh-aw.md index 8c950de9..f271fc0c 100644 --- a/docs/src/content/docs/integrations/gh-aw.md +++ b/docs/src/content/docs/integrations/gh-aw.md @@ -18,11 +18,9 @@ APM defines **what** agents know. gh-aw defines **when** and **how** they act. ## Integration Approaches -### Frontmatter Dependencies (Recommended) +### Shared apm.md Import (Recommended) -gh-aw natively supports APM through a [`dependencies:` frontmatter field](https://github.github.com/gh-aw/reference/frontmatter/#apm-dependencies-dependencies). Declare APM packages directly in your workflow's frontmatter and gh-aw handles the rest. - -**Simple array format:** +gh-aw ships a [shared `apm.md` workflow component](https://github.github.com/gh-aw/reference/dependencies/) that turns APM packages into gh-aw dependencies. Import it in your workflow's frontmatter and pass the packages you want. ```yaml --- @@ -31,9 +29,13 @@ on: types: [opened] engine: copilot -dependencies: - - microsoft/apm-sample-package - - github/awesome-copilot/skills/review-and-refactor +imports: + - uses: shared/apm.md + with: + packages: + - microsoft/apm-sample-package + - github/awesome-copilot/skills/review-and-refactor + - your-org/security-compliance#v1.4.0 --- # Code Review @@ -41,36 +43,33 @@ dependencies: Review the pull request using the installed coding standards and skills. ``` -**Object format with options:** +**Package reference formats:** -```yaml ---- -on: - issues: - types: [opened] -engine: copilot +| Format | Description | +|---|---| +| `owner/repo` | Full APM package (skills/agents/instructions under `.apm/`) | +| `owner/repo/path/to/primitive` | Individual primitive (skill, instruction, plugin, etc.) from any repository, regardless of layout | +| `owner/repo#ref` or `owner/repo/path/to/primitive#ref` | Pinned to a tag, branch, or commit SHA, for either a full package or a specific primitive | -dependencies: - packages: - - microsoft/apm-sample-package - - your-org/security-compliance - isolated: true ---- +The per-primitive path form is what makes `github/awesome-copilot/skills/review-and-refactor` work -- the awesome-copilot repo lays skills out at `/skills//`, not under `.apm/`. Use this form to consume skills from existing repositories without restructuring them. See [Anatomy of an APM Package](../../introduction/anatomy-of-an-apm-package/) for the full source-vs-output model. -# Issue Triage +**How it works:** -Analyze the opened issue for security implications. -``` +1. The gh-aw compiler detects the `shared/apm.md` import and adds a dedicated `apm` job to the compiled workflow. +2. The `apm` job runs `microsoft/apm-action` to install packages and uploads a bundle archive as a GitHub Actions artifact. +3. The agent job downloads and unpacks the bundle as pre-steps, making all primitives available at runtime. -Each entry is a standard APM package reference -- either `owner/repo` for a full package or `owner/repo/path/to/skill` for an individual primitive. +The APM compilation target is automatically inferred from the configured `engine:` field (`copilot`, `claude`, or `all` for other engines). No manual target configuration is needed. -**How it works:** +Packages are fetched using gh-aw's cascading token fallback: `GH_AW_PLUGINS_TOKEN` -> `GH_AW_GITHUB_TOKEN` -> `GITHUB_TOKEN`. -1. The gh-aw compiler detects the `dependencies:` field in your workflow frontmatter. -2. In the **activation job**, APM resolves the full dependency tree and packs the result. -3. In the **agent job**, the bundle is unpacked into the workspace and the agent discovers the primitives. +:::note[Isolated install by default] +`shared/apm.md` invokes `microsoft/apm-action` with `isolated: true`. Only the packages listed under `packages:` are installed -- any host-repo primitives under `.apm/` or `.github/` (instructions, prompts, skills, agents) are ignored and pre-existing primitive directories are cleared. To merge host-repo primitives with imported ones, use the [apm-action Pre-Step](#apm-action-pre-step) approach below, which leaves `isolated` at its default of `false`. +::: -The APM compilation target is automatically inferred from the configured `engine:` field (`copilot`, `claude`, or `all` for other engines). No manual target configuration is needed. +:::caution[Deprecated: `dependencies:` frontmatter] +Earlier gh-aw versions accepted a top-level `dependencies:` field on the workflow. That form is deprecated and no longer supported -- migrate to the `imports: - uses: shared/apm.md` pattern shown above. +::: ### apm-action Pre-Step @@ -112,48 +111,20 @@ For sandboxed environments where network access is restricted during workflow ex 1. Run `apm pack` in your CI pipeline to produce a self-contained bundle. 2. Distribute the bundle as a workflow artifact or commit it to the repository. -3. Reference the bundled primitives in your workflow. - -```yaml ---- -on: pull_request -engine: copilot -imports: - - .github/agents/code-reviewer.md - - .github/agents/security-auditor.md ---- - -# Code Review -Review the PR using team standards. -``` +3. Reference the bundled primitives directly from `.github/agents/` in your workflow. Bundles resolve full dependency trees ahead of time, so workflows need zero network access at runtime. -See the [CI/CD Integration guide](../ci-cd/) for details on building and distributing bundles. +See the [CI/CD Integration guide](../ci-cd/) and [Pack & Distribute](../../guides/pack-distribute/) for details on building and distributing bundles. For routing live install traffic through an enterprise proxy instead, see [Registry Proxy & Air-gapped](../../enterprise/registry-proxy/). ## Content Scanning -APM automatically scans dependencies for hidden Unicode characters during installation. Critical findings block deployment. This applies to both direct `apm install` and when GitHub Agentic Workflows resolves frontmatter dependencies via apm-action. +APM automatically scans dependencies for hidden Unicode characters during installation. Critical findings block deployment. This applies to both direct `apm install` and when gh-aw resolves packages via `shared/apm.md`. For CI visibility into scan results (SARIF reports, step summaries), see the [CI/CD Integration guide](../../integrations/ci-cd/#content-scanning-in-ci). For details on what APM detects, see [Content scanning](../../enterprise/security/#content-scanning). -## Isolated Mode - -When a gh-aw workflow runs in a repository that already has developer-focused instructions (like "use 4-space tabs" or "prefer functional style"), those instructions become noise for an automated agent that should only follow its declared dependencies. - -The `isolated` flag addresses this. When set to `true` in the object format: - -```yaml -dependencies: - packages: - - your-org/triage-rules - isolated: true -``` - -gh-aw clears existing `.github/` primitive directories (instructions, skills, agents) before unpacking the APM bundle. The agent sees only the context declared by the workflow, preventing instruction pollution from the host repository. - ## Learn More - [gh-aw Documentation](https://github.github.com/gh-aw/) diff --git a/docs/src/content/docs/introduction/anatomy-of-an-apm-package.md b/docs/src/content/docs/introduction/anatomy-of-an-apm-package.md new file mode 100644 index 00000000..cfa5bd8c --- /dev/null +++ b/docs/src/content/docs/introduction/anatomy-of-an-apm-package.md @@ -0,0 +1,302 @@ +--- +title: "Anatomy of an APM Package" +description: "What .apm/ is, why it exists, and how APM decides what is importable." +sidebar: + order: 5 +--- + +If you have read [What is APM?](./what-is-apm/) and [How It Works](./how-it-works/), +you know APM is a package manager for agent primitives. This page answers the +next question every user asks: what does an APM package actually look like on +disk, and why does it look that way? + +## The one-line mental model + +`apm.yml` is your `package.json`. `.apm/` is your `src/`. `apm_modules/` is your +`node_modules/`. The compiled output under `.github/`, `.claude/`, `.cursor/`, +and friends is your `dist/` -- generated, tool-specific, not the source of +truth. + +If you remember nothing else: **`.apm/` holds the primitives you author. +Everything outside `.apm/` that looks similar is either a build artifact or +someone else's package.** + +## Why `.apm/` exists + +AI coding tools each invented their own folder for context: `.github/` for +Copilot, `.claude/` for Claude Code, `.cursor/rules/` for Cursor, and so on. +Each one is read at runtime by exactly one tool. None of them are designed to +be authored portably, versioned as a dependency, or shared across tools. + +APM separates two concerns that those folders conflate: + +1. **Source primitives** -- the skills, agents, instructions, and prompts you + write and version. These live in `.apm/`. +2. **Compiled output** -- the tool-specific files APM generates from your + sources for each runtime you target. These live in `.github/`, `.claude/`, + `.cursor/`, etc. + +`apm install` and `apm compile` read from `.apm/` and write outward. + +### A concrete example: this repo + +The `microsoft/apm` repository (the one shipping the CLI you are reading docs +for) dogfoods this layout. It contains both source and compiled output side by +side: + +``` +microsoft/apm/ ++-- apm.yml ++-- .apm/ +| +-- skills/ +| | +-- writing-skills/ +| | +-- SKILL.md +| +-- instructions/ +| +-- agents/ ++-- .github/ +| +-- skills/ +| | +-- writing-skills/ +| | +-- SKILL.md (compiled from .apm/, byte-identical) +| +-- instructions/ +| +-- agents/ ++-- src/ ++-- tests/ +``` + +The file under `.apm/skills/writing-skills/SKILL.md` is the source. The file +under `.github/skills/writing-skills/SKILL.md` is the compiled artifact that +the in-repo Copilot agent actually loads while we work on the CLI. Same +content today, but only one of them is authoritative -- and only one of them +gets shipped when this repo is consumed as an APM package. + +## Why not just put primitives in `.github/` directly? + +It is tempting. `.github/` already exists, Copilot already reads it, why add +another folder? + +Three reasons, in order of severity. + +**1. Self-referential context pollution.** +The Copilot, Claude, or Cursor agent helping you author a skill reads +whatever sits in its runtime folder. If you author skills directly into +`.github/skills/`, your in-progress, half-written, possibly broken skill +becomes part of the system prompt of the agent you are using to write it. +Writing a code-review skill? Copilot starts applying it -- including to the +skill file itself -- before you have finished. Keeping sources in `.apm/` +means the dev-time agent only sees what you have explicitly compiled. + +**2. Portability across runtimes.** +A skill in `.github/skills/` is a Copilot-shaped file. A skill in +`.claude/skills/` is a Claude-shaped file. They are not interchangeable. The +whole point of APM is one source, many runtimes. That requires a +runtime-neutral source folder, and `.github/` is not it. + +**3. Packaging boundary.** +`apm pack` needs to know what is part of the package and what is incidental. +A dedicated `.apm/` directory makes that boundary trivial. Mixing sources +into `.github/` makes it a guessing game. + +## Why not the repo root? + +Also tempting, also wrong, for symmetric reasons: + +- **Naming collisions.** Most repos already have `skills/`, `agents/`, or + `prompts/` directories that mean something else (test fixtures, app code, + marketing copy). APM cannot safely claim those names at the root. +- **No discoverability signal.** A consumer cloning your repo cannot tell at + a glance whether it is an APM package. `.apm/` plus `apm.yml` is that + signal. +- **No clean pack boundary.** Same problem as `.github/`: `apm pack` would + need heuristics to know what to bundle. + +`.apm/` is short, namespaced, conventional, and unambiguous. That is the +whole argument. + +## Why not just ship a `plugin.json`? + +This is the sharpest version of the question, because plugin formats are +real and the ecosystem is converging on them. APM does not compete with +plugins -- it sits underneath them. + +- `plugin.json` is a **runtime distribution format**. It tells a single + host (Copilot CLI, Claude Code, Cursor) how to load a bundle of + primitives at runtime. +- `.apm/` is a **source layout**. It tells APM what you authored, so it + can resolve dependencies, lock versions, scan for security issues, and + compile to *every* runtime -- including plugin format. + +The two are complementary, and APM treats them that way: + +1. **APM consumes plugins as first-class dependencies.** Any repo with a + `plugin.json` (root, `.github/plugin/`, `.claude-plugin/`, or + `.cursor-plugin/`) is auto-recognized by `apm install`. APM + synthesizes an `apm.yml` from the plugin metadata so it gets version + pinning, lockfile entries, and transitive resolution. Marketplaces + (`marketplace.json`) resolve through the same path. See + [Plugins](../../guides/plugins/) and [Marketplaces](../../guides/marketplaces/). +2. **APM compiles `.apm/` to plugin format.** Run `apm pack --format + plugin` and you get a standalone plugin directory -- no `apm.yml`, no + `apm_modules/`, no `.apm/` -- consumable by any plugin host. See + [Pack & Distribute -- Plugin format](../../guides/pack-distribute/#plugin-format). +3. **Hybrid mode is supported.** A repo can ship `apm.yml` + `plugin.json` + together: author with APM (dependency management, lockfile, security + scanning, dev/prod separation), distribute as a standard plugin. + +What `plugin.json` alone does not give you: transitive dependency +resolution, a consumer-side lockfile, security scanning that blocks +critical findings on install, `devDependencies` that stay out of the +shipped artifact, or a single source that targets multiple runtimes. +That is the gap `.apm/` fills. If you only ever target one host and +never depend on shared primitives, plugin-only is fine -- and APM still +consumes you. + +## Two ways to be importable + +A repo can expose primitives to APM consumers in two forms. They are not +mutually exclusive. + +### Package form + +The repo declares itself an APM package: `apm.yml` at the root, primitives +under `.apm/`. Consumers reference it by repo name: + +```yaml +# consumer's apm.yml +dependencies: + apm: + - your-org/your-repo +``` + +`apm install` resolves the repo, reads its `apm.yml`, and pulls every +primitive declared in `.apm/` into `apm_modules/`. + +This is the right form when: + +- You are publishing a curated set of primitives meant to be consumed + together. +- You want a one-line install for the whole bundle. +- You want versioning, lockfile entries, and a clean update path. + +Canonical examples: [`microsoft/apm-sample-package`](https://github.com/microsoft/apm-sample-package), +[`apm-handbook`](https://github.com/danielmeppiel/apm-handbook) (a multi-package +monorepo with `apm.yml` plus `.apm/skills/` and `.apm/agents/`), and this +repository itself. + +### Primitive form + +Any subdirectory of any GitHub repo that looks like a primitive can be +imported directly by path. The upstream repo does not need an `apm.yml` and +does not need to use `.apm/`: + +```yaml +# consumer's apm.yml +dependencies: + apm: + - github/awesome-copilot/skills/review-and-refactor +``` + +APM treats the subdirectory as a virtual single-primitive package. + +This is the right form when: + +- You want one or two skills out of a large repo, not the whole thing. +- The upstream repo is not APM-aware (and you do not want to ask the + maintainer to refactor). +- You are pinning a specific primitive at a specific commit without taking + on the rest of the repo's surface area. + +Both forms produce the same artifact in `apm_modules/` and the same compiled +output. The reference syntax is the only difference. + +## Decision guide + +| Situation | Use | Does upstream need `.apm/`? | +|----------------------------------------------------------|----------------|-----------------------------| +| Importing one or two skills from a third-party repo | Primitive form | No | +| Publishing your team's full skill set as a bundle | Package form | Yes | +| Mixed: a curated bundle plus a few file-level imports | Package form | Yes (works for both) | +| Quick test before adopting someone's skill | Primitive form | No | + +The short version: **if you are consuming, primitive form covers most cases +without forcing anyone to refactor. If you are publishing, package form is +the right investment.** + +If you started authoring directly in `.github/` and later want to make a +proper package, the migration is mechanical: move the files into `.apm/`, +add an `apm.yml`, and run `apm install` to re-generate `.github/` from the +new source. No data loss, no breaking change for downstream consumers. + +## Why does microsoft/apm itself have a `.apm/` folder? + +Because we use APM to manage the agent context that develops APM. The +[concrete example above](#a-concrete-example-this-repo) is this repo. If +you are looking for a working reference layout, it is right there. + +## What APM looks for + +Discovery rules, in order: + +1. **`apm.yml`** at the repo root marks the directory as an APM package and + declares its dependencies, scripts, and metadata. +2. **`.apm/`** at the repo root is the source root for primitives. APM does + not look elsewhere for sources. +3. Inside `.apm/`, primitives are grouped by type subdirectory: + + ``` + .apm/ + +-- skills/ (SKILL.md plus supporting files) + +-- agents/ (agent definitions) + +-- instructions/ (instruction files) + +-- prompts/ (prompt templates) + +-- chatmodes/ (chat mode configurations) + +-- context/ (shared context fragments) + ``` + +4. **Per-primitive references** (`owner/repo/path/to/primitive`) bypass + `.apm/` entirely. APM treats the named subdirectory as a single-primitive + virtual package regardless of where it sits in the upstream repo. +5. **Compiled output** (`.github/`, `.claude/`, `.cursor/rules/`, and other + runtime targets) is generated by `apm compile` based on the runtimes + declared in `apm.yml`. Never edit these directly in an APM-managed repo. + +For the full schema, see [Manifest Schema](../../reference/manifest-schema/) +and [Primitive Types](../../reference/primitive-types/). + +## Quick FAQ + +**I edited `.github/skills/my-skill/SKILL.md` directly. What happens on the +next `apm install`?** Your edit gets overwritten. Edit the source under +`.apm/skills/my-skill/SKILL.md` instead and re-run `apm install`. + +**I ran `ls` and don't see `.apm/`.** It's a dotfile directory, hidden by +default. Use `ls -a`. + +**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 +touch `.apm/`. + +**What's the minimum for a valid APM package?** `apm.yml` at the root plus +at least one primitive under `.apm/`. + +**Isn't the industry converging on the plugin format? Why do I need +`.apm/` at all?** APM consumes plugins natively (`plugin.json` packages +install as first-class dependencies) and exports to plugin format +(`apm pack --format plugin`). `.apm/` is the source layout that gives +you dependency management, lockfiles, and security scanning during +authoring; `plugin.json` is the runtime distribution format. Use both -- +see [Why not just ship a `plugin.json`?](#why-not-just-ship-a-pluginjson) +above and the [hybrid authoring workflow](../../guides/plugins/#hybrid-authoring-workflow). + +## See also + +- [Your First Package](../../getting-started/first-package/) -- create a + package from scratch using this layout. +- [Primitive Types](../../reference/primitive-types/) -- the canonical + reference for skills, agents, instructions, prompts, and friends. +- [Manifest Schema](../../reference/manifest-schema/) -- the full `apm.yml` + spec. +- [gh-aw Integration](../../integrations/gh-aw/) -- how compiled output + feeds GitHub Agentic Workflows. +- [Compilation](../../guides/compilation/) -- how `.apm/` becomes + `.github/`, `.claude/`, and the rest. diff --git a/docs/src/content/docs/introduction/how-it-works.md b/docs/src/content/docs/introduction/how-it-works.md index e0bc3c61..a6f94e47 100644 --- a/docs/src/content/docs/introduction/how-it-works.md +++ b/docs/src/content/docs/introduction/how-it-works.md @@ -114,7 +114,7 @@ graph TD **Key Architecture Components**: -1. **Context** (.apm/ directory) - Your source code for AI workflows +1. **Context** (.apm/ directory) - Your source code for AI workflows. See [Anatomy of an APM Package](../anatomy-of-an-apm-package/) for the directory layout. 2. **APM CLI** - Three core engines working together: - **Package Manager** - Dependency resolution and distribution - **Primitives Compiler** - Transforms primitives → agents.md format diff --git a/docs/src/content/docs/introduction/key-concepts.md b/docs/src/content/docs/introduction/key-concepts.md index a66e363f..2f6664f4 100644 --- a/docs/src/content/docs/introduction/key-concepts.md +++ b/docs/src/content/docs/introduction/key-concepts.md @@ -71,40 +71,12 @@ The APM CLI supports the following types of primitives: > **Note**: Both `.agent.md` (new format) and `.chatmode.md` (legacy format) are fully supported. VSCode provides Quick Fix actions to help migrate from `.chatmode.md` to `.agent.md`. -## File Structure +## Where primitives live -### Supported Locations - -APM discovers primitives in these locations: - -``` -# APM-native structure -.apm/ -├── agents/ # AI assistant definitions (new format) -│ └── *.agent.md -├── chatmodes/ # AI assistant definitions (legacy format) -│ └── *.chatmode.md -├── instructions/ # Coding standards and guidelines -│ └── *.instructions.md -└── hooks/ # Lifecycle event handlers - ├── *.json # Hook definitions (JSON) - └── scripts/ # Referenced scripts - └── *.sh, *.py - -# VSCode-compatible structure -.github/ -├── agents/ # VSCode Copilot agents (new format) -│ └── *.agent.md -├── chatmodes/ # VSCode Copilot chatmodes (legacy format) -│ └── *.chatmode.md -└── instructions/ # VSCode Copilot instructions - └── *.instructions.md - -# Generic files (anywhere in project) -*.agent.md -*.chatmode.md -*.instructions.md -``` +Primitives are authored in `.apm/` and deployed to runtime folders +(`.github/`, `.claude/`, `.cursor/`, `.opencode/`) by `apm install` and +`apm compile`. For the full layout, source-vs-output distinction, and +discovery rules, see [Anatomy of an APM Package](../anatomy-of-an-apm-package/). ## Component Types Overview @@ -385,16 +357,7 @@ Use specific `applyTo` patterns for instructions: Keep primitives in version control alongside your code. Use semantic versioning for breaking changes. ### 4. Organized Structure -Use the structured `.apm/` directories for better organization: -``` -.apm/ -├── agents/ -│ ├── code-reviewer.agent.md -│ └── documentation-writer.agent.md -└── instructions/ - ├── python-style.instructions.md - └── typescript-conventions.instructions.md -``` +Use `.apm/` subdirectories by primitive type. See [Anatomy](../anatomy-of-an-apm-package/#what-apm-looks-for). ### 5. Team Collaboration - Include author information in frontmatter @@ -403,16 +366,7 @@ Use the structured `.apm/` directories for better organization: ## Integration with VSCode -For VSCode Copilot compatibility, place files in `.github/` directories: -``` -.github/ -├── agents/ -│ └── assistant.agent.md -└── instructions/ - └── coding-standards.instructions.md -``` - -These files follow the same format and will be discovered alongside APM-specific primitives. +VS Code Copilot reads compiled output in `.github/`. Author in `.apm/` and let `apm install` produce it -- see [Anatomy](../anatomy-of-an-apm-package/) for the source-vs-output model. ## Error Handling