chore: drop the duplicated package descriptions from root apm.yml

Why: for a local-path marketplace entry, apm treats a root description: as
an override of the package's own apm.yml, reported only at verbose level,
so the "two copies stay identical" rule had no enforcement. Same fix as
2def060 made for version:.

Implementation Notes:
- All six root copies matched their package apm.yml before removal; the
  compiled marketplace.json descriptions are unchanged.
- apm-workflow references now scope the "omit it" advice to local-path
  entries: on a remote entry, version: is the semver range that selects
  the tag, and version: or ref: is required.

Impact: ADR-0021 amended; the package apm.yml is the single source.

ADR: 0021
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 11:24:22 +00:00
parent c5d43b4d63
commit 398515bcad
4 changed files with 53 additions and 20 deletions

View File

@@ -75,31 +75,25 @@ marketplace:
packages:
- name: kyberforge
description: Skills and agents for creating, maintaining, and managing a Claude Code / Copilot CLI plugin marketplace.
source: ./plugins/kyberforge
category: Developer Tools
- name: bin
description: Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.
source: ./plugins/bin
category: Utilities
- name: git
description: Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.
source: ./plugins/git
category: Version Control
- name: gitea
description: Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.
source: ./plugins/gitea
category: Version Control
- name: core
description: Skills for authoring and auditing a repo's AGENTS.md and the provider adapter files that defer to it.
source: ./plugins/core
category: Productivity
- name: lint
description: Skills and agents for configuring and running linters.
source: ./plugins/lint
category: Developer Tools

View File

@@ -18,6 +18,9 @@ file, `.claude-plugin/marketplace.json`. Read the "four generated files" in Cont
generated files" in Consequences as historical counts, true when written. The blast radius shrank;
the staleness hazard that motivated this ADR did not.
**Amended 2026-09-16:** the root `marketplace.packages[]` copy of each description was removed; the
package `apm.yml` is now the single source. See the amendment before Consequences.
## Context
A plugin's published description is one string authored twice — in `plugins/<name>/apm.yml` and in
@@ -203,6 +206,32 @@ sit inside ADR-0020's tiers; the tier would have been silent through all three f
string, not a link — and the README's own plugin list carries the same enumeration with the same
staleness, so this relocates the defect rather than fixing it.
## Amendment (2026-09-16): the root copy is removed — the package `apm.yml` is the single source
The Decision's rule that "the two copies … stay identical" is retired by removing the second copy.
The six `description:` lines under root `apm.yml`'s `marketplace.packages[]` are deleted, the same way
`2def060` deleted the six `version:` lines beside them. `plugins/<name>/apm.yml`'s `description:` is
now the only place a package's published description is authored.
The rule's own justification — "the root entry is what reaches the compiled marketplace" — was true
only while the root entry set the field. apm's Claude marketplace mapper resolves a local-path entry's
`description` curator-first: the entry's value wins when present, and when it is absent the value is
read from the package's own `apm.yml` (`apm_cli/marketplace/output_mappers.py`, the `is_local` branch
calling `_apply_field_with_precedence` with `source_label="package apm.yml"`). The root copy was
therefore an override, not a mirror. Nothing enforced the identity rule, and on drift apm silently
published the root value. Removing the copy removes the drift rather than leaving it unchecked.
All six root copies were byte-identical to their package's `apm.yml` when they were removed. After
the removal, `apm pack` regenerated `.claude-plugin/marketplace.json` with every `description` unchanged,
and `apm pack --check-versions --check-clean --dry-run` passes. The consequence for the "unbounded
obligation" in Context is that a description edit is now one edit, not two. The package version bump
and the catalog patch bump it earns are unchanged
(`plugins/kyberforge/.apm/skills/apm-workflow/references/configure.md`).
This applies to local-path (`source: ./…`) entries only. A remote entry has no local package
`apm.yml` to fall back to. Its `description:`, when set, is still the published text, and when it
is absent apm uses whatever its best-effort remote metadata fetch returns.
## Consequences
**Three descriptions are rewritten and the compiled output regenerated.** Eight generated files

View File

@@ -53,13 +53,19 @@ output changes.** Two triggers, not one:
The version belongs to the package, not to the repo: editing `plugins/foo/.apm/` never bumps
`plugins/bar/apm.yml`.
Under a `per_package` strategy this `version:` is the single source: when a catalog's
`marketplace.packages[]` entry omits `version:`, `apm pack` reads it from the package's `apm.yml`.
Do not restate it there. A `version:` on the entry is an override, not a copy — it silently wins in
the compiled `marketplace.json`, and `apm pack --check-versions` still reports `[matches]` when it
disagrees with the package's own number, so drift between the two is never caught. Set one only
when an override is the intent. A skipped bump is not caught either: nothing infers intent from a
content diff. The catalog's own version follows a separate rule — see `references/marketplace.md`.
Under a `per_package` strategy this `version:` is the single source for a local-path
(`source: ./…`) catalog entry: when that `marketplace.packages[]` entry omits `version:`,
`apm pack` reads it from the package's `apm.yml`. Do not restate it there. On a local entry a
`version:` is an override, not a copy — it silently wins in the compiled `marketplace.json`, and
`apm pack --check-versions` still reports `[matches]` when it disagrees with the package's own
number, so drift between the two is never caught. Set one only when an override is the intent.
`description:` behaves the same way: a local entry that omits it publishes the package `apm.yml`'s
`description`, and one that sets it silently overrides it. A remote entry is different. There,
`version:` is the semver range that selects which git tag to resolve, and a remote entry must carry
`version:` or `ref:`. Its `description:`, when set, is the published text.
A skipped bump is not caught either: nothing infers intent from a content diff. The catalog's own
version follows a separate rule — see `references/marketplace.md`.
## Dependency reference forms

View File

@@ -68,10 +68,13 @@ marketplace:
Without this block, the default versioning strategy ties every listed package to the marketplace/root version.
With it, each entry's version comes from its package's own `apm.yml`. An entry may also set
`version:`, but that is an override: it silently wins in the compiled `marketplace.json`, and
`apm pack --check-versions` does not flag the mismatch. Omit it unless an override is intended —
see `references/configure.md`.
With it, each local-path (`source: ./…`) entry's version comes from its package's own
`apm.yml`, and so does its `description`. A local entry may also set `version:` or `description:`,
but either one is an override: it silently wins in the compiled `marketplace.json`, and
`apm pack --check-versions` does not flag a version mismatch. Omit both unless an override is
intended — see `references/configure.md`. A remote entry is different: its `version:` is the semver
range that selects the git tag to resolve (matched through the entry's `tag_pattern`, else
`build.tagPattern`), not an override, and the entry must set `version:` or `ref:`.
## Bumping the catalog's own version (repo policy)
@@ -86,9 +89,10 @@ earned it:
- **Patch** when the set of packages is unchanged but what the catalog publishes moved: a
`marketplace:`-block field (the catalog description, owner, `build:`/`outputs:` config, or an
existing entry's description or category), or any listed package's own `apm.yml` `version:`. The
most frequent trigger by far is the package bump. The root entry carries no `version:`, but the
compiled `marketplace.json` publishes each package's version, so a package bump alone changes the
catalog and alone earns the patch — made in the same commit (see `references/configure.md`).
most frequent trigger by far is the package bump. When entries omit `version:` (the recommended
local form), the compiled `marketplace.json` still publishes each package's version, so a package
bump alone changes the catalog and alone earns the patch — made in the same commit (see
`references/configure.md`).
Keep the root `apm.yml`'s top-level `version:` in step with `marketplace.version`. They are separate
keys — the top-level one is not inherited into the compiled `marketplace.json`, but `apm audit`