# Microsoft APM replaces the hand-authored plugin/marketplace model as this repo's authoring source of truth **Status: executed (2026-08-12, issue #90).** All six plugins now carry `apm.yml` + `.apm/` as their authoring source; `.claude-plugin/marketplace.json` and every plugin's `plugin.json` are `apm pack`-compiled output. **Supersedes ADR-0001** ("Skills are distributed via plugins... each plugin contains its own `skills/` directory") — in effect. This repo replaces its hand-maintained Claude Code plugin/marketplace authoring model (`.claude-plugin/marketplace.json` + per-plugin `plugin.json`) with Microsoft APM (`apm.yml` + `.apm/`) as the authoring source of truth — an outright replacement of the authoring layer, not an additive overlay. This ADR records the decision from a `grill-with-docs` session on issue #88. ## Context Every plugin under `plugins//` currently ships two hand-maintained manifests (`.claude-plugin/plugin.json` for Claude Code, root `plugin.json` for Copilot CLI) plus a hand-maintained root `.claude-plugin/marketplace.json` listing all plugins. Adding a provider means hand-authoring a third manifest shape; keeping the two existing ones in parity is itself a tracked concern (ADR-0006). Research on Microsoft APM (`plugins/kyberforge/docs/research/docs/microsoft-apm/`) found that its documented "monorepo-hybrid" repo shape maps directly onto this repo's existing `plugins//` layout: each plugin becomes its own `apm.yml` + `.apm/{skills,agents,hooks,prompts,instructions}/` package, listed from a root `apm.yml`'s `marketplace:` block. `apm compile`/`apm pack` generate per-target output — including a `.claude-plugin/marketplace.json` — from that vendor-neutral `.apm/` tree, so provider manifests become compiled artifacts instead of hand-authored files, and new providers (Copilot, Gemini, Codex — all supported by `apm runtime setup`) no longer require a new hand-maintained manifest format. ## Decision - **The `plugins//` monorepo-hybrid directory layout survives.** `.claude-plugin/marketplace.json` and per-provider `plugin.json` files become **compiled output** via `apm compile`/`apm pack`, generated from `apm.yml` + `.apm/` per plugin, extensible to other `apm runtime`-supported providers without hand-maintaining a separate manifest per provider. - **This supersedes ADR-0001** ("Skills are distributed via plugins... each plugin contains its own `skills/` directory"). Executed in issue #90: skills and agents physically moved to `plugins//.apm/skills/` and `plugins//.apm/agents/*.agent.md`. - New operational tooling — `apm-install` (skill), `apm-workflow` (skill), `apm-orchestrate` (agent) — lands in `kyberforge`, tracked in issue #88 (https://git.dev.rkdr.net/Defame1297/holocron/issues/88). - Adapting `skill-author`/`agent-author`'s routing to author `.apm/`-native content (retargeting to `.apm/skills/`, `.apm/agents/` paths — the content these two skills author is still meaningful post-conversion) is deferred to issue #89 (https://git.dev.rkdr.net/Defame1297/holocron/issues/89). `forge` is out of scope for #89 — it stays untouched by this whole conversion effort and keeps routing to whatever the live author skills are at the time. - **`plugin-author`/`marketplace-author` are not adapted — they are superseded and deleted.** Unlike `skill-author`/`agent-author`, nothing in these two skills carries forward as authoring routing: `apm compile`/`apm pack` will generate `.claude-plugin/marketplace.json` and per-provider `plugin.json` directly from `apm.yml` + `.apm/`, so `apm-install`/`apm-workflow`/ `apm-orchestrate` (issue #88, already landed on this branch) fully replace what these two skills did. `plugin-author`/`marketplace-author` were deleted in issue #90's execution. - Translating the existing plugins into `apm.yml` + `.apm/` and running the real conversion was executed under issue #90 (https://git.dev.rkdr.net/Defame1297/holocron/issues/90), which tracks that work through to merge. - `CONTEXT.md`'s "Plugin"/"Plugin marketplace" glossary entries were rewritten in issue #90 to describe the compiled-output model directly, rather than carrying a forward-pointer to this ADR. Superseded 2026-08-17: CONTEXT.md was cut back to one-line definitions, and the compiled-output model is now described in `docs/spec/architecture.md`. ## Considered options **Additive/compile-layer only, no `apm.yml` (rejected).** Keep `plugin.json`/`marketplace.json` hand-authored and bolt APM on top as an optional extra. Rejected: doesn't achieve the multi-provider compile-reuse goal APM's package model provides, and leaves the existing dual-manifest hand maintenance in place unchanged. **New standalone `plugins/apm/` plugin (rejected).** `plugins/lint/` was split out of `kyberforge` specifically because Vale tooling is generic and repo-agnostic, not holocron-marketplace-specific (see `CONTEXT.md`'s "lint plugin" entry) — the same argument applies to a generic `apm` CLI wrapper. The shipped `apm-install`/`apm-workflow` skills are, in fact, generic, repo-agnostic APM CLI documentation with no holocron-specific content, so a standalone `plugins/apm/` would have been a defensible split on artifact content alone. Rejected anyway, in favor of `kyberforge`, because holocron is currently the only repo that needs this tooling — standing up a separate plugin for a single consumer isn't worth it yet. Accepted as an explicit tradeoff (same pattern as ADR-0011's `gitea-workflow` naming tradeoff) — worth revisiting if this tooling is ever reused outside holocron's own conversion. ## Content migration out of `plugin-author`/`marketplace-author` A content audit of `plugin-author`/`marketplace-author` (same `grill-with-docs` session as this correction) sorted what they document into three buckets: - **Claude Code platform constraints — carried forward.** Facts that stay true regardless of authoring model (reserved plugin-name prefixes; the `agents/`-directory stray-`.md`-file validator gotcha, ADR-0010; `claude plugin validate` as a required terminal check) have been added into `apm-workflow`'s reference docs, since compiled output still has to satisfy these constraints post-conversion. - **Dual-manifest artifacts — obsolete, not carried forward.** Conventions that existed only because of hand-authored dual manifests (ADR-0006's version-parity/patch-bump rule, the CC-vs-Copilot field-placement split, dual-file mirroring) are obsolete under `apm.yml`'s single-manifest model and were deliberately dropped. - **Holocron policy choice — resolved in #90.** `marketplace-author`'s catalog-version convention (minor bump for package add/remove, patch bump for field-only updates) isn't an APM mechanic — `apm` doesn't enforce it, and has no native version-bump automation at all — so rather than building a new script, the convention is now documented as guidance inside `apm-workflow`'s reference docs (`references/marketplace.md` for the root catalog version rule, `references/configure.md` for the per-package version-bump-on-content-edit rule), applied manually by whoever edits `apm.yml`. ## Consequences - ADR-0001 is superseded (issue #90). - ADR-0006 (plugin-version-parity) is moot (issue #90): `plugin.json`/`marketplace.json` are now compiled output of a single `apm.yml`, so there's no second hand-authored file left to keep in parity, and `plugin-author` — the skill that enforced ADR-0006 — was deleted rather than adapted (see "Content migration" above). - ADR-0010 (agent sources relocated outside agents dir) was updated (issue #90) for agents now living at `plugins//.apm/agents/*.agent.md` — the directory path changed; the pre-existing `.agent.md` extension convention (ADR-0005/ADR-0010) and project/user scope are unaffected, per ADR-0016. - ADR-0014 (Vale prefilter ships from the plugin) had its hardcoded `plugins//skills/...` paths (the Vale prefilter is skill-scoped only; ADR-0014 never referenced a `plugins//agents/...` path) updated for the `.apm/` nesting as part of issue #90's execution. - `kyberforge` gained three new artifacts (issue #88) before any conversion of existing content happened, then lost two (`plugin-author`/`marketplace-author`, deleted once issue #90 verified parity) — net version bump 1.3.1 → 1.4.0. The root marketplace catalog bumped 0.3.1 → 0.3.2 to match. - ADR-0016 (a narrower decision discovered while designing issue #89) turned out to gate how issue #90 had to re-author plugin-scope agents: `.apm/agents/*.agent.md` compiles verbatim to both Claude and Copilot, so those files carry only the fields in the `apm-agent-allowlist` section of `plugins/kyberforge/.apm/skills/agent-audit/references/field-inventory.md` (as amended 2026-08-14: `name`/`description`/`model`/`source_keys`/`disallowedTools`) — existing dual-file `.md`+`.agent.md` pairs could not be raw-moved, only re-authored. - Two follow-up issues tracked the remaining work: #89 (`skill-author`/`agent-author` routing adaptation — closed, merged in #93) and #90 (the actual repo conversion, which also deleted `plugin-author`/`marketplace-author` — tracked through to merge; treat #90's own state as the authority on whether it has landed, not this line). - **`displayName` is gone from all six compiled `plugin.json` files — accepted, not overlooked.** `apm.yml` has no key that compiles to it: `synthesize_plugin_json_from_apm_yml` (`apm_cli/deps/plugin_parser.py`) emits only `name`, `version`, `description`, `author`, `license`, `homepage`, `repository` and `keywords`, and nothing in `plugin_manifest.py` adds `displayName` afterwards. So every `plugins//.claude-plugin/plugin.json` now carries `author`/`description`/`homepage`/`keywords`/`license`/`name`/`repository`/`version` (plus `mcpServers` for `bin`) and no `displayName`. The field is optional — `plugins/kyberforge/docs/research/docs/claude-code-plugins/api-reference.md:14` lists `displayName` as `Required: No`, "Human-readable name shown in plugin manager" — which is why `claude plugin validate --strict` still passes on all six. The visible cost is that the plugin manager falls back to the bare `name` as each plugin's label. Accepted as the price of `apm.yml` being the single authoring source: re-injecting `displayName` post-compile would mean a second `reinject_*` workaround of the kind ADR-0017's amendment reserves for fields apm strips on a factually wrong premise, and apm's premise here is simply that the key does not exist in its schema. - **`owner.email` was dropped by mistake and has been restored (2026-08-14).** An earlier revision of this ADR listed `owner.email` alongside `displayName` as a field `apm.yml` "has no key that compiles to." That was wrong. `apm_cli/marketplace/yml_schema.py:186` defines `_AUTHOR_OBJECT_KEYS = frozenset({"name", "email", "url"})`, and an `email:` under root `apm.yml`'s `marketplace.owner` block was empirically confirmed to compile straight through into `.claude-plugin/marketplace.json`'s `owner`. The key is declared in root `apm.yml` again and the compiled `owner` block is `{name, email, url}`. Only `displayName` is a genuine schema gap; this one was a documentation error that removed working configuration. - **`mattpocock-skills` is pinned to an exact version, and the pin is advanced by hand.** Pre-conversion the entry was `{"repo": "mattpocock/skills", "source": "github"}` — an unpinned reference that tracked the upstream default branch, so consumers got whatever was on it at install time. The conversion first replaced that with `version: "^1.2.0"`, which was still not a pin: a caret range has nothing to freeze it, because there is no lockfile for `marketplace.packages[]`. `apm pack` re-resolved the range against upstream on **every** run, so an upstream `v1.2.4` would immediately invalidate the committed `ref`/`sha` and fail `apm-pack-check-clean` with exit 4 — blocking every push in the repo, triggered by a third party at an unrelated moment, with no local change to explain it. Root `apm.yml` therefore declares an exact `version: "1.2.3"`, which `apm pack` freezes into `.claude-plugin/marketplace.json` as `ref: v1.2.3` + an explicit `sha`. Two consequences, both intended: the committed ref/sha is genuinely reproducible and cannot move under the repo, and picking up a new upstream release is a deliberate act — a human edits the `version:` string in root `apm.yml` and re-runs `apm pack`. apm has no version-bump automation (established under "Versioning" in issue #90's plan), so an ageing pin is the accepted cost of a push gate that only fires on this repo's own changes. Note the pin does not make the entry offline-resolvable: an exact version still requires a `git ls-remote`, which is why two pre-push hooks need the network (see `AGENTS.md`). - **Caveat on "Status: executed" above:** issue #90's own execution comment flagged, before merge, that Claude Code's ability to actually load content out of `.apm/` was unverified — that caveat turned out to be a real defect, not a formality: the native installer has zero awareness of `.apm/` and reported `Skills (0) Agents (0) Hooks (0)` on every plugin installed from this marketplace. The manifest-compilation deliverable this ADR describes was genuinely complete; runtime discoverability was not. Fixed in ADR-0017 (a second, compiled flat-directory content mirror at each plugin root, generated by `scripts/sync-plugin-content.sh`) — see that ADR for the root cause and the fix.