Skip to content

Skill marketplace: marketplace.json + plugin packaging for native Claude Code install #277

Description

@rucka

Story Statement

As a Claude Code user
I want marketplace.json + plugin packaging, and an assistant skill that sets pair up and then helps me use it
So that /plugin marketplace add foomakers/pair gets me a working pair install natively — the plugin for zero-setup discovery, then one skill to converge on a single, CLI-generated tree instead of two

Where: .claude-plugin/ at the repo root of foomakers/pair (consumed by Claude Code's native plugin/marketplace mechanism), plus packages/knowledge-hub/dataset/.skills/capability/setup-cli/ for the setup skill

Refined 2026-07-31 (scope widened, agreed with the maintainer): the story originally stopped at the manifests. It now also owns the bootstrap path off the marketplace channel — the setup skill — and the exclude mechanism that path needs. Rationale in Scope additions at the bottom; the two halves ship together because the manifests without the bootstrap leave the channel in the state this story's own ADL documents as a known limitation (adoption files resolve to pair's own, and the both-installed resolution order was never observed).

Epic Context

Parent Epic: Skill marketplace + Quickstart + external KB scaffold #213
Status: Refined
Priority: P0 (Must-Have)

Status Workflow

  • Refined: Story is detailed, estimated, and ready for development
  • In Progress: Story is actively being developed
  • Done: Story delivered and accepted

Classification

Refinement-time matrix, authored 2026-07-31 with the scope widening. It was missing until now — the story went to Refined without one, which is why PR #386 had to establish a review-time matrix instead. Recorded here so the two can be compared at the merge gate rather than the review's being the only record.

Dimension Value Why
Service/domain criticality 🟡 yellow tech/risk-matrix.md declares no criticality table → KB default Medium
Change/diff risk 🟡 yellow Three surfaces now: root .claude-plugin/ manifests, the CLI install pipeline (apps/pair-cli/src/registry/, packages/content-ops copy paths), and one new skill in the corpus. No schema, migration or infra change; the pipeline change is additive behind an option.
Business impact 🟡 yellow Supporting subdomain — skill distribution and bootstrap. The KB/skill corpus itself (core) gains one entry but is otherwise untouched.
Security relevance 🟡 yellow The setup skill runs a package-manager install and may git init in the user's working directory. Bounded deliberately: only commands the user would run by hand, echoed before execution, no network beyond the package registry, no credentials read or written. Not red — no authn/secrets/PII path, and nothing runs unattended.
Coupling balance 🟢 green The exclusion travels the route flatten/prefix already travel; no new cross-context integration. The skill is isolated by construction (no KB references).

Tier = max = 🟡 yellowrisk:yellow. Cost class: 🟢 green — no paid SDK, no provisioning, no LLM/API call added.

Acceptance Criteria

Functional Requirements

Given-When-Then Format:

  1. Given the foomakers/pair repo with .claude-plugin/marketplace.json present
    When a Claude Code user runs /plugin marketplace add foomakers/pair and /plugin install pair@pair
    Then the pair plugin installs with no pair-cli step — and what installs is the bootstrap skill, not the catalog: claude plugin details pair@pair reports Skills (1) pair-assistant, Agents (0), Hooks (0), MCP servers (0), LSP servers (0)

  2. Given the marketplace entry's source points at the plugin root (./packages/knowledge-hub/dataset/plugin)
    When the plugin is installed
    Then the plugin cache holds that root's .claude-plugin/ and skills/ and nothing else — in particular no .pair/, so no adoption file can be read from the cache as if it were the project's, and none can be written there to be lost by the next /plugin update

    Revised 2026-07-31. This AC previously read "lists every currently-shipped skill from dataset/.skills/". That was the whole-repository payload, and it is what produced the two limitations above. The catalog now reaches a project only through the CLI; the manifest declares the bootstrap corpus, and a guard derives the expected list from that corpus on disk.

  3. Given a skill is added to, renamed in, or removed from the distributed catalog (dataset/.skills/)
    When the next release ships
    Then no manifest edit is required — the plugin declares the bootstrap corpus only. A manifest edit is required when a bootstrap skill is added, and the catalog guard fails naming the exact entry.

  4. Given both the plugin and a CLI-installed project
    When a skill is invoked by its bare /pair-… name
    Then no name resolves from two sources: the plugin declares only pair-assistant, which is authored outside dataset/.skills/ and therefore never installed by the CLI. Keeping both installed is supported — the "pick one channel" rule is retired, not merely relaxed.

  5. Given a Claude Code user who installed the plugin
    When they invoke /pair-assistant
    Then it installs pair-clilocally (dev dependency, using the manager the lockfile names) when a package.json is present, otherwise via npx — and then runs the CLI to produce .pair/ and .claude/skills/, so every path is the CLI's own

  6. Given the working directory is not a git repository
    When the setup skill runs
    Then it creates the repository first and continues with the same commands — a handled precondition, never a failure

  7. Given the setup skill has finished
    When it reports
    Then it states what now exists in the project, that the plugin still holds the bootstrap skill only (so nothing is duplicated), and hands off to /pair-next

  8. Given pair is already installed in the project
    When the user asks the assistant to do something with the CLI, or asks how the project does something
    Then it answers from two runtime sources, never from a list written into the skill: the CLI's surface via pair-cli --help / <command> --help, and the project's standards via its own .pair/llms.txt, read relative to the working directory and quoted with the file named

    Why runtime rather than inlined: the repo already holds two copies of the CLI surface (the code and the published reference, pinned to each other by docs:staleness); a third inside the plugin would have no guard, and the version installed in a user's project may differ from the one the skill was written against.

  9. Given the assistant ships inside the plugin cache
    When its content is checked
    Then it carries no link into .pair/knowledge/** or .pair/adoption/** — a relative link out of the payload resolves to pair's own KB or to nothing — while a runtime read of the project's .pair/llms.txt from the working directory is explicitly allowed, because that is the user's own installed KB. The guard matches link targets precisely so the useful half stays legal.

  10. Given the assistant is run in a repository that already has a codebase and no .pair/
    When the preflight classifies it
    Then it does not install silently: it states that the adoption layer installs with add behaviour (so nothing the user already has is overwritten — a fact, not a promise), then asks whether to install pair only or to install and adopt this project into it, describing what each writes. Option 2 hands off to /pair-process-bootstrap, which owns the adoption checklist and is in the catalogue the install just delivered. With no answer, the safe option is taken.

  11. Given the assistant answers from the project's .pair/llms.txt
    When the index names a path that does not exist
    Then it does not guess: it lists .pair/knowledge/ to find the file and reports that the index is stale. Nothing gates llms.txt against the KB it describes (issue tech-debt: no gate covers .pair/llms.txt drift — the generated KB index goes stale silently #416), so the skill treats it as a table of contents and prefers what a file says over what the index says about it — and always names the file or command an answer came from.

  12. Given the brownfield path hands off to /pair-process-bootstrap
    When the adoption pass runs on a project that already exists
    Then it is driven with the project's context rather than a fresh interview: the assistant passes the evidence it already gathered (package manager, code host, existing CI), and bootstrap does the deep reading itself — it resolves each value through the convention's cascade (explicit argument > project state > recorded decision > KB fallback) and skips phases whose output already exists. The assistant builds no detector of its own; /pair-capability-assess-stack and /pair-capability-setup-gates own that reading.

  13. Given the cascade is defined for every decision point
    When bootstrap's quick mode resolves the architecture, infrastructure and observability sections
    Then it reads project state first — workspace/layout shape, Dockerfile, compose, terraform/, k8s manifests, platform config, workflow files and their environment names, telemetry dependencies — and falls back to the KB rows only where the repository is silent. Those three were fallback outright, which meant quick wrote a default with the evidence in plain sight; a default is for silence, not a shortcut past looking.

  14. Given assertBootstrapSkillsValid replaces skills:conformance for a corpus outside dataset/.skills/
    When a bootstrap skill's frontmatter drifts
    Then the gate fails: name must equal the directory (no transform assigns it here), with a non-empty description and a semver version

Business Rules

  • The setup skill is isolated by construction: it carries no reference to .pair/knowledge/** or to any adoption file. It has to run before the KB exists in the project, and on the marketplace channel any KB it could read would be pair's own — so a KB reference would be either dangling or wrong. This is what "isolated skill" means for this story, and it is the property that makes the skill valid on a channel where nothing else adoption-dependent is.
  • exclude is kept as a general capability, with no consumer (maintainer call, 2026-07-31). Its original job — keeping the setup skill out of consuming projects — is done structurally instead, by authoring that skill outside dataset/.skills/. The mechanism stays, tested on both copy paths plus a smoke scenario, and the ADL says plainly that nothing uses it today.
  • One channel, one entry point (revises D1): the CLI is how pair reaches a project; the marketplace is the zero-setup way to get the CLI running. Same source of truth: packages/knowledge-hub.
  • No named role agents distributed (D23, R9.3) — the marketplace manifest lists skills only; subagents are anonymous context-isolation mechanics, never a distributed "agent" asset.
  • Hand-maintained, not generated: the manifest is a release-checklist item, not a build artifact — avoids adding marketplace-manifest generation to the release pipeline for a file that changes only when the skill catalog itself changes.

Edge Cases and Error Handling

  • Skill renamed/removed without a marketplace.json update: stale manifest entry points at a skill that no longer exists at that path — caught only by the release checklist, not automatically; flag as a known manual-process risk (see Notes).
  • No package.json in the working directory: the CLI cannot be added as a dependency, so the skill falls back to npx and says which mode it chose — silence here would leave the user unsure whether a tool was added to their project.
  • A package manager other than the repo's own: the skill detects the manager from the lockfile rather than defaulting to one, because installing with the wrong manager writes a second lockfile.
  • pair-cli already installed: the install step is skipped, not repeated; the skill proceeds straight to the CLI run.
  • Both channels installed at once: no longer an error state (AC4) — the two share no skill name. The setup skill says so explicitly rather than leaving the user to wonder.
  • Claude Code plugin format changes upstream: this story's schema is a snapshot of Claude Code's plugin/marketplace format at implementation time — a Spike (see Technical Analysis) verifies the exact current schema before writing the manifest, since it's an external, evolving spec pair doesn't control.

Definition of Done Checklist

Development Completion

  • All acceptance criteria implemented and verified
  • .claude-plugin/marketplace.json (and any companion plugin.json) created, matching Claude Code's current plugin/marketplace schema
  • Code review completed and approved
  • Documentation updated — docs site (marketplace install path, alongside the existing CLI install docs)

Quality Assurance

  • Manual test: /plugin marketplace add <path> actually installs the skills — DONE pre-merge (review round 2, CLI v2.1.220). claude plugin marketplace add accepts a URL, a path, or a GitHub repo, so the marketplace-resolution path (the one piece claude --plugin-dir does NOT exercise) is testable before merge. Sandboxed via CLAUDE_CONFIG_DIR against the branch head: marketplace add <path> → "Successfully added marketplace: pair"; plugin install pair@pair → "Successfully installed"; claude plugin details pair@pairSkills (40) exactly as declared, Agents (0), Hooks (0), MCP servers (0), LSP servers (0), Version: <commit sha> (confirming the ADL Decision 4 SHA fallback). Also: claude plugin validate . passes (one expected version warning) and claude --plugin-dir <repo> loads all 40 skills (pair:pair-*) from outside the repo. AC1 is verified, not inferred. Residual gap: only GitHub-shorthand resolution of foomakers/pair, which genuinely resolves after merge.
  • Cross-check: every skill listed in the manifest exists in packages/knowledge-hub/dataset/.skills/ with a matching name

Story Sizing and Sprint Readiness

Refined Story Points

Final Story Points: 2 (S)
Confidence Level: Medium
Sizing Justification: hand-maintained manifest (no generation pipeline) keeps this small; uncertainty is entirely in the external Claude Code plugin schema, not in pair's own logic — hence a Spike, not extra story points

Sprint Capacity Validation

Sprint Fit Assessment: yes, single sprint
Total Effort Assessment: fits — Yes

Dependencies and Coordination

Story Dependencies

Prerequisite Stories: none
Dependent Stories: none — independent of Quickstart (story #278) and the external KB scaffold (story #279); all three are parallel tracks under this epic

Validation and Testing Strategy

Acceptance Testing Approach

Testing Methods: manual install test via a real Claude Code session (/plugin marketplace add foomakers/pair), followed by invoking at least one installed skill to confirm it actually runs
Test Data Requirements: none — tested directly against the real repo

Notes and Additional Context

Refinement Session Insights: Decision confirmed during refinement — marketplace.json is hand-maintained, not auto-generated from the skill catalog (simplicity over automation for a low-frequency-change manifest); this is a deliberate tradeoff, revisit if manifest drift becomes a recurring real problem.
Documentation Links: Requirements R9.1 · Spec G11 · Decisions D1, D23 · pair-requirements-triage.md G11 section

Technical Analysis

Implementation Approach

Technical Strategy: static, hand-authored .claude-plugin/ directory at the repo root (mirroring the real-world precedent already observed in other Claude Code plugin repos, e.g. mattpocock/skills' own .claude-plugin/plugin.json), listing pair's skills for native marketplace install. No new CLI code, no new build step — purely a repo-root content addition plus a release-checklist entry.
Key Components: .claude-plugin/marketplace.json (marketplace listing), .claude-plugin/plugin.json (plugin manifest, if Claude Code's schema requires both — confirm during the Spike)
Integration Points: none with pair-cli — this channel is entirely independent of the CLI install path (D1)

Technical Requirements

  • Manifest content must stay in sync with packages/knowledge-hub/dataset/.skills/ at release time (hand-maintained, per a release-checklist item — not enforced by tooling)
  • No named-agent assets in the manifest (D23) — skills only

Technical Risks and Mitigation

Risk Impact Probability Mitigation Strategy
Claude Code's plugin/marketplace schema is external and may change Medium Medium Spike verifies the current schema before implementation; re-verify at each major Claude Code release
Hand-maintained manifest drifts from the real skill catalog over time Low Medium Add a release-checklist line item; revisit automation if drift is observed in practice

Spike Requirements

Required Spikes: verify Claude Code's current plugin/marketplace manifest schema (field names, required vs. optional, whether both plugin.json and marketplace.json are needed, or one suffices) directly against Claude Code's own documentation/examples before writing the manifest.
Estimated Spike Effort: 1-2 hours

Task Breakdown

Checklist

  • T1 — Spike: confirm Claude Code's current plugin/marketplace manifest schema
  • T2 — Author .claude-plugin/marketplace.json (+ plugin.json if required) listing pair's skill catalog
  • T3 — Manual install verification + docs site update + release-checklist entry

Dependency Graph

T1 → T2 → T3

AC Coverage

Task AC1 AC2 AC3 AC4
T1
T2
T3

Scope additions (agreed 2026-07-31)

1. Registry-level exclude — the enabler

Widened deliberately: the exclusion is the enabler for #392's setup-assistant channel, so it lands where it is used rather than as a standalone mechanism nobody consumes yet.

A registry may declare exclude: [...] — source-relative entries the copy never installs. Threaded config → RegistryConfigSyncOptions → the copy pipeline, the same route flatten/prefix already travel.

Why it belongs to this story: the marketplace channel will ship a setup skill that must exist only when installed from the marketplace. Without the exclusion the CLI also writes it into the project, and the same skill name then answers from two trees — ~/.claude/plugins/cache and the project's .claude/skills/ — whose resolution order this story's own ADL records as never observed (verified only with the plugin as the sole source). An execution ambiguity, not a cosmetic duplicate.

Design points, each asserted: matching is per path segment, never a string prefix (excluding process/setup must not drop process/setup-helper); an excluded entry's whole subtree is skipped; filtering happens before validation and before any mkdir, so an excluded entry cannot contribute a flatten collision, a directory mapping or a link-rewrite pass; absent ⇒ byte-identical previous behaviour.

Known limit, recorded in the code and in #392: this prevents writing an entry, not having it. The skills registry runs behavior: overwrite, which performs no cleanup, so an excluded entry already present in a target is left untouched.

Also fixed here: the catalog guard flagged pair-process-brainstorm missing from plugin.json after #387 merged — the first time that guard fired on a real omission. Catalog now at 41 entries.

2. The setup skill — the bootstrap this channel was missing

pair-assistant, invoked as /pair-assistant. It closes the gap the manifests leave open: after /plugin install pair@pair a user has the skills but no project files, and every adoption-dependent skill silently reads pair's adoption instead of theirs (this story's ADL records that as a known limitation). The skill turns the plugin channel from "skills that read the wrong project" into "one command, then a CLI-generated tree".

What it does, in order: detect the package manager and whether a package.json exists → install pair-cli locally, or fall back to npx → ensure a git repository exists, creating one if not → run the CLI install → report the duplicate-tree state if the plugin is still installed, with the single-channel remedy.

Where the exclusion lives, and the one special case it creates. apps/pair-cli/config.json excludes pair-assistant, so no consuming project installs it. But in this repository .claude/skills/ is the plugin payload, and it is normally regenerated with pair update — the very command that now skips this entry. Since the skills registry runs behavior: overwrite (no cleanup), a stale copy would survive and the mirror-equality guard would fail with a remedy that cannot fix it. So this one skill's mirror is regenerated with knowledge-hub's transfer-dataset instead, and the guard's message says so. Accepted cost: one documented special case, chosen over the alternative (no exclusion, and a useless skill installed into every CLI project).

Consequences for the catalog: the dataset goes to 42 skills, so plugin.json gains an entry and the skill-count claims in README.md and the Claude Code integration page go 41 → 42. Both counts are gate-pinned, so neither can drift silently.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions