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
6 changes: 6 additions & 0 deletions .apm/skills/pr-description-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ complete.
Run these steps in order. Tick each before moving on.

1. [ ] Confirm every row of the activation contract is filled in.
Defense-in-depth gate: before drafting the body, confirm the
repo's lint contract is green (canonical commands and lifecycle
binding live in the project's `copilot-instructions.md` Linting
block - do NOT inline or restate them here). If lint is red,
STOP, fix, re-run; a PR body claiming green CI while lint fails
is a credibility tax we refuse to take on.
2. [ ] Read the diff in full. Identify per-file change summary,
new files, deleted files, behavior changes at module
boundaries.
Expand Down
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
- **Full suite (only before final commit):** `uv run pytest`
- When modifying a specific module, run only its corresponding test file(s) first. Run the full unit suite once as final validation before considering your work done.
- **Test coverage principle**: When modifying existing code, add tests for the code paths you touch, on top of tests for the new functionality.
- **Linting (run BEFORE pushing - CI gate fails otherwise)**: The `Lint` job runs `uv run --extra dev ruff check src/ tests/` AND `uv run --extra dev ruff format --check src/ tests/`. Mirror it locally:
- **Auto-fix style+imports:** `uv run --extra dev ruff check src/ tests/ --fix`
- **Apply formatter:** `uv run --extra dev ruff format src/ tests/`
- **Verify (must be silent):** `uv run --extra dev ruff check src/ tests/ && uv run --extra dev ruff format --check src/ tests/`
- Always run the verify pair before `git push` -- the CI Lint job fails on any remaining diagnostic. Common surprises: `RUF043` (use `match=r"..."` for regex with metacharacters), `UP006/UP045` (use `list`/`dict`/`X | None` instead of `List`/`Dict`/`Optional`), `RUF100` (drop stale `# noqa`), `F401`/`F841` (unused import / unused local).
- **Lifecycle binding**: this rule is the canonical lint contract for the repo. Any skill that produces an artifact claiming green CI -- notably `pr-description-skill` (whose "Validation evidence" row covers CI checks) -- inherits this gate transitively. Do NOT redefine ruff commands inside individual skills; honor this rule before invoking them.
- **Development Workflow**: To run APM from source while working in other directories:
- Install in development mode: `cd /path/to/awd-cli && uv run pip install -e .`
- Use absolute path: `/Users/danielmeppiel/Repos/awd-cli/.venv/bin/apm compile --verbose --dry-run`
Expand Down
6 changes: 6 additions & 0 deletions .github/skills/pr-description-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ complete.
Run these steps in order. Tick each before moving on.

1. [ ] Confirm every row of the activation contract is filled in.
Defense-in-depth gate: before drafting the body, confirm the
repo's lint contract is green (canonical commands and lifecycle
binding live in the project's `copilot-instructions.md` Linting
block - do NOT inline or restate them here). If lint is red,
STOP, fix, re-run; a PR body claiming green CI while lint fails
is a credibility tax we refuse to take on.
2. [ ] Read the diff in full. Identify per-file change summary,
new files, deleted files, behavior changes at module
boundaries.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **BREAKING: `apm pack` now produces a Claude Code plugin directory by default — zero extra flags, schema-validated `plugin.json`, convention dirs auto-discovered.** The legacy APM bundle layout is preserved under `--format apm`. Migration: CI workflows and scripts that consume the legacy bundle must add `--format apm` (the [`microsoft/apm-action`](https://github.com/microsoft/apm-action) wrapper has been updated accordingly). (#1061)
- **Plugin manifest schema conformance.** The synthesized/written `plugin.json` no longer emits `agents`/`skills`/`commands`/`instructions` keys pointing at the convention directories — these are auto-discovered by Claude Code, and per the [official schema](https://json.schemastore.org/claude-code-plugin.json) those array entries must be `./*.md` paths to *additional* files. The convention dirs themselves are still copied to disk. When stripping such keys from an authored `plugin.json`, `apm pack` now emits a warning so authors can clean up their source. (#1061)

### Added

- **`apm pack` marketplace builder hardening.** Local source paths are now emitted relative to `metadata.pluginRoot` (fixes double-prefix bug). New pass-through fields: `author`, `license`, `repository`, `keywords` (alias for `tags`). Curator-wins override semantics for `description`/`version` on remote entries. Security guards reject path traversal and absolute paths post-subtraction. (#1061)
- **Plugin manifest schema-conformance tests.** `tests/unit/test_plugin_exporter_schema.py` validates every shape of `plugin.json` produced by `apm pack` (synthesized, authored, and authored-with-stale-keys) against the vendored official schema. Companion marketplace conformance lives in `tests/unit/marketplace/test_schema_conformance.py`. (#1061)
- Slash commands installed from APM packages now surface argument hints in Claude Code -- `apm install` automatically maps prompt `input:` to Claude's `arguments:` front-matter, rewrites `${input:name}` references to `$name`, and auto-generates `argument-hint`. Argument names are validated against an allowlist to prevent YAML injection from third-party packages, and the mapping is reported at install time. (#1039)

## [0.11.0] - 2026-04-29
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/enterprise/registry-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ connected host, transport it, restore it offline.
export PROXY_REGISTRY_URL=https://art.corp.example.com/artifactory/github
export PROXY_REGISTRY_ONLY=1
apm install
apm pack --archive -o ./artifacts/
apm pack --format apm --archive -o ./artifacts/

# Transport ./artifacts/*.tar.gz to the air-gapped network

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/enterprise/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ When APM deploys a file, it checks whether a file already exists at the target p
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.
- **Development dependencies** (`devDependencies.apm`, installed via `apm install --dev`) are resolved and cached locally but **excluded** from `apm pack` output (both plugin format -- the default -- and `--format apm`).

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.

Expand Down
10 changes: 6 additions & 4 deletions docs/src/content/docs/getting-started/first-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,22 +242,24 @@ consumers. This lets you target plugin-aware hosts (Copilot CLI plugins,
the broader plugin ecosystem) with the primitives you already authored.

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

Output:
Output (plugin format is the default):

```
build/team-skills-1.0.0/
+-- plugin.json # synthesized from apm.yml
+-- plugin.json # synthesized, schema-conformant per https://json.schemastore.org/claude-code-plugin.json
+-- agents/
| +-- team-reviewer.agent.md
+-- skills/
+-- pr-description/SKILL.md
```

No `apm.yml`, no `apm_modules/`, no `.apm/`. Just primitives in
plugin-native layout.
plugin-native layout. Convention dirs (`agents/`, `skills/`, `commands/`,
`instructions/`) are auto-discovered by Claude Code, so the synthesized
`plugin.json` does not list them.

If you know up front that you want to ship a plugin, you can scaffold with
`apm init --plugin team-skills`, which adds `plugin.json` next to `apm.yml`
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,9 @@ devDependencies:
- 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.
Dev dependencies install to `apm_modules/` like production deps but are excluded from `apm pack` 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.
**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` (plugin format, the default). 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

Expand Down
8 changes: 4 additions & 4 deletions docs/src/content/docs/guides/dev-only-primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ devDependencies:
- 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.
`apm install --dev` deploys the release-checklist skill to `.github/skills/release-checklist/` with `is_dev: true` in the lockfile. `apm pack` (plugin format, the default) excludes it. Consumers running `apm install your-org/your-package` never see it.

## Why outside `.apm/`?

Expand All @@ -49,7 +49,7 @@ Authoring dev-only primitives anywhere outside `.apm/` (`dev/`, `internal/`, `.m

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.
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` time, not at install time.

## When to use this pattern

Expand All @@ -68,7 +68,7 @@ Authoring dev-only primitives anywhere outside `.apm/` (`dev/`, `internal/`, `.m
```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
apm pack --dry-run # confirm absence from bundle (plugin format, default)
ls build/your-package-1.0.0/skills/ # release-checklist must NOT appear
```

Expand All @@ -79,4 +79,4 @@ If the dev-only skill appears in the dry-run output, it is sitting under `.apm/`
- [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.
- [Pack & Distribute -- Plugin format](./pack-distribute/#plugin-format-vs-apm-format) -- what the scanner emits.
16 changes: 12 additions & 4 deletions docs/src/content/docs/guides/marketplace-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,14 @@ Stripped from `marketplace.json` at compile time.
|-------|----------|-------------|
| `name` | yes | Plugin name consumers will install. Unique within the marketplace. |
| `source` | yes | Either `<owner>/<repo>` (remote) or `./path/to/dir` (local-path entry in this repo). |
| `description` | no | Pass-through to `marketplace.json`. |
| `description` | no | Pass-through to `marketplace.json`. For remote entries, overrides the remote-fetched description (curator-wins). |
| `homepage` | no | Pass-through URL. |
| `tags` | no | Pass-through list of strings. Omitted from output when empty. |
| `version` | conditional | Semver range (see below). Either `version` or `ref` must be set for remote sources. Local sources may set `version` to seed the compiled output. |
| `tags` | no | Pass-through list of strings. Omitted from output when empty. Max 50 items, 100 chars each. |
| `keywords` | no | Alias for `tags`. Merged with `tags` (deduplicated). Same limits apply to the combined list. |
| `author` | no | Pass-through string (e.g. `"ACME Corp"`). Must be a string if set. |
| `license` | no | Pass-through string (e.g. `"MIT"`). Must be a string if set. |
| `repository` | no | Pass-through URL string (e.g. `"https://github.com/org/repo"`). Must be a string if set. |
| `version` | conditional | Semver range (see below). Either `version` or `ref` must be set for remote sources. For remote entries, a fixed version (not a range) is emitted as display metadata (curator-wins override). Local sources may set `version` to seed the compiled output. |
| `ref` | conditional | Explicit SHA, tag, or branch. Takes precedence over `version`. Remote sources only. |
| `subdir` | no | Subdirectory within a remote repo. Validated against path traversal. |
| `tag_pattern` | no | Per-plugin override of `build.tagPattern`. |
Expand All @@ -166,7 +170,11 @@ Unknown keys inside `marketplace:` raise a schema error rather than being silent

### Local-path entries

When `source` starts with `./`, the entry is a local-path plugin: APM does not run `git ls-remote`, does not resolve a SHA, and emits the path verbatim into `marketplace.json` as a plain string source. Use this for plugins that ship in the same repository as the marketplace itself (the azure-skills pattern).
When `source` starts with `./`, the entry is a local-path plugin: APM does not run `git ls-remote`, does not resolve a SHA, and emits the path into `marketplace.json` as a plain string source.

If `metadata.pluginRoot` is set, local source paths are emitted **relative to pluginRoot** in the output. For example, with `pluginRoot: ./plugins`, a source of `./plugins/my-tool` is emitted as `./my-tool`. This prevents double-prefix bugs where consumers prepend pluginRoot to an already-rooted path.

If the source path does not start with pluginRoot, it is emitted verbatim and a build warning is produced.

```yaml
plugins:
Expand Down
Loading
Loading