You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a Claude Code user I wantmarketplace.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).
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 = 🟡 yellow → risk:yellow. Cost class: 🟢 green — no paid SDK, no provisioning, no LLM/API call added.
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
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)
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.
Given a skill is added to, renamed in, or removed from the distributed catalog (dataset/.skills/) When the next release ships Thenno 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.
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.
Given a Claude Code user who installed the plugin When they invoke /pair-assistant Then it installs pair-cli — locally (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
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
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
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.
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.
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.
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.
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.
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.
GivenassertBootstrapSkillsValid 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@pair → Skills (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
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 → RegistryConfig → SyncOptions → 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.
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 itSo that
/plugin marketplace add foomakers/pairgets 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 twoWhere:
.claude-plugin/at the repo root offoomakers/pair(consumed by Claude Code's native plugin/marketplace mechanism), pluspackages/knowledge-hub/dataset/.skills/capability/setup-cli/for the setup skillRefined 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
excludemechanism 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
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.
tech/risk-matrix.mddeclares no criticality table → KB default Medium.claude-plugin/manifests, the CLI install pipeline (apps/pair-cli/src/registry/,packages/content-opscopy paths), and one new skill in the corpus. No schema, migration or infra change; the pipeline change is additive behind an option.git initin 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.flatten/prefixalready travel; no new cross-context integration. The skill is isolated by construction (no KB references).Tier = max = 🟡 yellow →
risk:yellow. Cost class: 🟢 green — no paid SDK, no provisioning, no LLM/API call added.Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given the
foomakers/pairrepo with.claude-plugin/marketplace.jsonpresentWhen a Claude Code user runs
/plugin marketplace add foomakers/pairand/plugin install pair@pairThen the
pairplugin installs with nopair-clistep — and what installs is the bootstrap skill, not the catalog:claude plugin details pair@pairreportsSkills (1) pair-assistant,Agents (0),Hooks (0),MCP servers (0),LSP servers (0)Given the marketplace entry's
sourcepoints at the plugin root (./packages/knowledge-hub/dataset/plugin)When the plugin is installed
Then the plugin cache holds that root's
.claude-plugin/andskills/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 updateGiven 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.
Given both the plugin and a CLI-installed project
When a skill is invoked by its bare
/pair-…nameThen no name resolves from two sources: the plugin declares only
pair-assistant, which is authored outsidedataset/.skills/and therefore never installed by the CLI. Keeping both installed is supported — the "pick one channel" rule is retired, not merely relaxed.Given a Claude Code user who installed the plugin
When they invoke
/pair-assistantThen it installs
pair-cli— locally (dev dependency, using the manager the lockfile names) when apackage.jsonis present, otherwise vianpx— and then runs the CLI to produce.pair/and.claude/skills/, so every path is the CLI's ownGiven 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
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-nextGiven 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 namedGiven 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.txtfrom 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.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
addbehaviour (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.Given the assistant answers from the project's
.pair/llms.txtWhen 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 gatesllms.txtagainst 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.Given the brownfield path hands off to
/pair-process-bootstrapWhen 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-stackand/pair-capability-setup-gatesown that reading.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 werefallbackoutright, which meant quick wrote a default with the evidence in plain sight; a default is for silence, not a shortcut past looking.Given
assertBootstrapSkillsValidreplacesskills:conformancefor a corpus outsidedataset/.skills/When a bootstrap skill's frontmatter drifts
Then the gate fails:
namemust equal the directory (no transform assigns it here), with a non-empty description and a semver versionBusiness Rules
.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.excludeis 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 outsidedataset/.skills/. The mechanism stays, tested on both copy paths plus a smoke scenario, and the ADL says plainly that nothing uses it today.packages/knowledge-hub.Edge Cases and Error Handling
marketplace.jsonupdate: 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).package.jsonin the working directory: the CLI cannot be added as a dependency, so the skill falls back tonpxand says which mode it chose — silence here would leave the user unsure whether a tool was added to their project.pair-clialready installed: the install step is skipped, not repeated; the skill proceeds straight to the CLI run.Definition of Done Checklist
Development Completion
.claude-plugin/marketplace.json(and any companionplugin.json) created, matching Claude Code's current plugin/marketplace schemaQuality Assurance
/plugin marketplace add <path>actually installs the skills — DONE pre-merge (review round 2, CLI v2.1.220).claude plugin marketplace addaccepts a URL, a path, or a GitHub repo, so the marketplace-resolution path (the one piececlaude --plugin-dirdoes NOT exercise) is testable before merge. Sandboxed viaCLAUDE_CONFIG_DIRagainst the branch head:marketplace add <path>→ "Successfully added marketplace: pair";plugin install pair@pair→ "Successfully installed";claude plugin details pair@pair→Skills (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) andclaude --plugin-dir <repo>loads all 40 skills (pair:pair-*) from outside the repo. AC1 is verified, not inferred. Residual gap: only GitHub-shorthand resolution offoomakers/pair, which genuinely resolves after merge.packages/knowledge-hub/dataset/.skills/with a matching nameStory 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 runsTest Data Requirements: none — tested directly against the real repo
Notes and Additional Context
Refinement Session Insights: Decision confirmed during refinement —
marketplace.jsonis 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.mdG11 sectionTechnical 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
packages/knowledge-hub/dataset/.skills/at release time (hand-maintained, per a release-checklist item — not enforced by tooling)Technical Risks and Mitigation
Spike Requirements
Required Spikes: verify Claude Code's current plugin/marketplace manifest schema (field names, required vs. optional, whether both
plugin.jsonandmarketplace.jsonare 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
.claude-plugin/marketplace.json(+plugin.jsonif required) listing pair's skill catalogDependency Graph
T1 → T2 → T3
AC Coverage
Scope additions (agreed 2026-07-31)
1. Registry-level
exclude— the enablerWidened 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 →RegistryConfig→SyncOptions→ the copy pipeline, the same routeflatten/prefixalready 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/cacheand 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/setupmust not dropprocess/setup-helper); an excluded entry's whole subtree is skipped; filtering happens before validation and before anymkdir, 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-brainstormmissing fromplugin.jsonafter #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@paira 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.jsonexists → installpair-clilocally, or fall back tonpx→ 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.jsonexcludespair-assistant, so no consuming project installs it. But in this repository.claude/skills/is the plugin payload, and it is normally regenerated withpair update— the very command that now skips this entry. Since the skills registry runsbehavior: 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'stransfer-datasetinstead, 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.jsongains an entry and the skill-count claims inREADME.mdand the Claude Code integration page go 41 → 42. Both counts are gate-pinned, so neither can drift silently.