fix(kyberforge): bridge apm content to Claude Code's flat plugin discovery

Claude Code's (and Copilot's) native plugin installer has zero awareness of
.apm/ nesting -- it convention-scans only flat skills/, agents/, commands/,
hooks.json at each plugin's root. Confirmed via strings on the installed
claude binary and live installs of git@holocron/gitea@holocron/kyberforge@
holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm
conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json
compiler deliberately strips skills/agents/commands keys, assuming the host
already auto-discovers those convention directories -- it has no model of
.apm/ being host-visible at all. Separately, apm's own bundle exporter
(apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`)
implements the correct .apm/ -> flat mapping, but only ever targeted
build/<name>-<version>/, a path nothing in marketplace.json's source: points
at.

scripts/sync-plugin-content.sh wraps that bundle exporter and copies its
agents/, skills/, commands/, instructions/, extensions/, and merged
hooks.json back into each plugin's own root as a second tracked
compiled-output category -- same governance status as
.claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/
subdirectories are excluded from the mirror (dev fixtures, not host-visible
runtime content; several hardcode a relative repo-root walk-up sized for the
.apm/-nested depth, which breaks when duplicated one level shallower).
Applied for real across all 6 plugins and verified two ways: `claude plugin
validate --strict` passes on every real plugin directory, and a live
`claude --plugin-dir <path> -p "list skills/agents"` behavioral test
confirms content is now actually discovered.

Also, from the same issue #90 review round:
- scripts/check-manifests.sh pointed at each plugin's root-level plugin.json
  (checking skills/hooks/mcpServers/agents pointer fields) -- that file was a
  stale near-duplicate of .claude-plugin/plugin.json nothing else read or
  wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to
  validate .claude-plugin/plugin.json instead, and drops the pointer-field
  checks entirely (nothing to check -- those fields are correctly absent by
  design). Content-presence drift is now check-plugin-content-sync's job, a
  new pre-push hook wired in .pre-commit-config.yaml.

docs/adr/0017 records the root cause and decision in full, including two
rejected alternatives (patching plugin.json's path fields directly -- apm's
compiler strips them on every run; pointing marketplace.json at apm pack's
build/ output -- a version-suffixed non-source directory nothing can install
from without an extra build step). ADR-0015 and CONTEXT.md are updated to
point at it.

Refs: #90
This commit is contained in:
2026-08-13 16:59:03 +00:00
parent 7910b8b12c
commit 38f1ba4e03
217 changed files with 14455 additions and 175 deletions

View File

@@ -0,0 +1,63 @@
---
topic: configure
source_keys:
- context7-microsoft-apm
---
## Scaffolding a new package
```bash
apm plugin init --yes --target claude,copilot
```
Run from inside the target package directory, with no positional name argument (see Gotchas). Creates `apm.yml` + `plugin.json` in the current directory — it does NOT scaffold a `.apm/` skeleton. Primitive subdirectories (`.apm/skills/`, `.apm/agents/`, `.apm/hooks/`) must be created manually as content is migrated into them. Run this once per package (e.g. once per `plugins/<name>/` directory in a monorepo-hybrid layout), not once for the whole repo.
## `apm.yml` — required fields
Only `name` and `version` (SemVer) are required:
```yaml
name: my-pkg
version: 1.0.0
```
## `apm.yml` — top-level keys
- `name`, `version` — required (see above)
- `description`, `author`, `license`, `homepage`, `repository`, `keywords` — standard package metadata
- `type` — `instructions | skill | hybrid | prompts`; constrains `.apm/` contents
- `targets` — which harnesses this package compiles to (plural list form preferred; legacy singular `target: copilot,claude` CSV form still accepted)
- `includes` — `auto` publishes the authoritative local layout as-is, or list explicit repo paths. Note: `auto` does not sweep generic root-level passthrough files (README.md, docs/, sources.md, config files) into the `apm pack` distribution bundle — see `references/compile.md`
- `dependencies`/`devDependencies` — `apm`/`mcp`/`lsp` entries; `devDependencies` share the same shape but are excluded from the shipped artifact
- `scripts` — named commands runnable via `apm run <name>`
- `compilation` — target/strategy/exclude/placement controls for `apm compile`/`apm pack`
- `policy` — e.g. `fetch_failure_default`
- `registries` — named registry endpoints for shorthand dependency resolution
- `marketplace` — owner + packages list; see `references/marketplace.md` for the full marketplace workflow
See `docs/research/docs/microsoft-apm/configuration.md` for the complete annotated schema.
## Dependency reference forms
`dependencies.apm` entries accept: a pinned tag (`owner/repo#tag`), a plain repo (uses default branch), a single primitive path within a repo, a raw git URL, a `git:`/`path:`/`ref:` object for finer control, or a local relative path (`./packages/my-shared-skills`).
## MCP server secrets
`${VAR}` indirection is required for MCP server secrets in `apm.yml` — see SKILL.md Gotchas.
## Registries (config-level, not `apm.yml`)
Any git repo is a valid package source by default — no registry required. To declare named registries for shorthand dependency resolution:
```bash
apm experimental enable registries # required first — see SKILL.md Gotchas
apm config set registry.corp-main.url https://artifactory.corp.example.com/apm
apm config set registry.corp-main.token eyJ...
apm config set registry.corp-main.default true
```
`apm config get`/`apm config unset` manage individual keys the same way.
## Gotchas
- `apm plugin init <name>` run with a positional name argument, from inside a directory already named `<name>`, creates a wrongly-nested `<name>/<name>/` subdirectory — it treats the positional arg as "create a new project directory named X," not "confirm the current directory is X." Fix: omit the positional argument entirely when already cd'd into the target package directory — run `apm plugin init --yes --target claude,copilot` instead.