Give Claude Code users one-command, per-skill installs: teach scripts/build-dist.mjs to emit a curated skills/ catalog into dist/, so npx skills add shawn-sandy/agentics-kit --skill <name> -a claude-code drops a hand-picked, standalone-safe skill into ~/.claude/skills/ without adopting the whole plugin — zero new workflow wiring, source repo layout untouched, and the same catalog remains installable by other skills-CLI agents as a bonus.
Read and implement all steps in the plan at docs/plans/distribute-skills-via-skill-box-catalog.html — Publish a curated skills/ catalog for per-skill Claude Code installs
Run as workflow — launch parallel subagents
Run a workflow to implement the plan at docs/plans/distribute-skills-via-skill-box-catalog.html — Publish a curated skills/ catalog for per-skill Claude Code installs
distribute-skills-via-skill-box-catalog.html
docs/plans/distribute-skills-via-skill-box-catalog.html
Context
The repo distributes plugins through the Claude Code plugin marketplace (.claude-plugin/marketplace.json → scripts/build-dist.mjs → published to agentics-kit by .github/workflows/publish-dist.yml). The vercel-labs/skills CLI (npx skills) is a separate, cross-agent channel that installs individual skills (any directory with a SKILL.md carrying name + description) into Claude Code (./.claude/skills/ or ~/.claude/skills/) — and into 70+ other agents, though Claude Code users are the primary audience here. For a Claude user the catalog's value is granularity: install one curated skill without adopting its whole plugin via /plugin install.
That CLI discovers skills under a root skills/ directory (flat skills/<name>/SKILL.md or catalog skills/<category>/<name>/SKILL.md). This repo nests skills at kit/plugins/<plugin>/skills/<name>/SKILL.md — too deep for a bare repo-level install to find. Option 3 closes that gap by having build-dist.mjs emit a curated skills/ catalog into dist/, which the existing publish step copies to the agentics-kit root.
Decisions already made (do not re-litigate):
- Catalog home:
dist/only → published toagentics-kit. Install surface isnpx skills add shawn-sandy/agentics-kit. Source repo stays clean; no new workflow wiring. - Curation: an explicit allowlist in
scripts/skill-catalog.jsonplus a build-time lint guard — not a heuristic. - Layout: catalog form
dist/skills/<plugin>/<skill>/…, copying the whole skill directory (skill dirs carryreferences/,assets/,scripts/).
Hard constraints discovered during investigation:
- The skills CLI copies only the skill directory — no sibling
commands/,agents/, orhooks/. Any skill whoseSKILL.mdbody invokes a/<plugin>:<command>, spawns a plugin agent, or relies on a hook is broken when installed standalone. Curation must exclude these. - A heuristic like "plugins with no
commands/dir" is unreliable:kit/plugins/issue-agent/skills/create-issue/SKILL.mdreferences a slash command despite issue-agent being skills-only. - On the user's machine the CLI flattens installs to
.claude/skills/<name>/, so cataloged skill names must be globally unique. They are today (no duplicateSKILL.mddirectory names across plugins) — the build must enforce this so a future collision fails the build, not the user's install. - Skill dirs carry real payload (58 supporting files across
references/,assets/,scripts/,reference/). The copy must take the whole dir. - Frontmatter: every
SKILL.mdalready hasname+description. The extraallowed-toolskey is Claude-specific and ignored by other agents — leave it.
Files to Modify
- scripts/
build-dist.mjsmodified buildSkillCatalog() pass + check() extensionskill-catalog.jsonnew curated allowlist of exported skills- tests/publish/
smoke-clean-dist.shmodified assert catalog presence in disttest-skill-catalog.mjsnew catalog validation test.claude/rules/marketplace.mdmodified name the curation surface.github/workflows/publish-dist.ymlmodified run the new catalog testCHANGELOG.mdmodified [Unreleased] entry for the skill-box channelCLAUDE.mdmodified note the skill-box channelREADME.mdmodified npx skills add install instructionsdocs/plans/distribute-skills-via-skill-box-catalog.htmlnew this plan — commit with the changestests/fixtures/skill-catalog/new valid + invalid stub SKILL.md fixtures
Diagram
kit/plugins/<plugin>/skills/<skill>/scripts/skill-catalog.jsonbuildSkillCatalog() in scripts/build-dist.mjsdist/skills/<plugin>/<skill>/.github/workflows/publish-dist.yml → agentics-kitnpx skills add shawn-sandy/agentics-kit --skill <name> -a claude-code- memory-tools/agentic-memory-doctor
- memory-tools/path-rules-advisor
- wcag-compliance-reviewer/wcag-compliance-reviewer
- skill-reviewer/*
- code-testing-agent/reviewing-tests
- issue-agent/create-issue (slash-command ref)
Steps
scripts/skill-catalog.json — the curated allowlist
"<plugin>/<skill>" relative to kit/plugins/<plugin>/skills/; only standalone-safe skills (no command, agent, or hook dependency) go in. Seed with memory-tools/agentic-memory-doctor, memory-tools/path-rules-advisor, wcag-compliance-reviewer/wcag-compliance-reviewer. Then resolve the "verify, then add" candidates deterministically: run the Step 2 guard against skill-reviewer/* and code-testing-agent/reviewing-tests — passers join the allowlist; exclusions are recorded in an excluded map inside skill-catalog.json with a one-line reason each (JSON carries no comments).Verify
kit/plugins/<plugin>/skills/<skill>/SKILL.md (all three starter entries do today). The excluded map documents every audited-but-excluded skill with its reason. Starter shortlist confirmed before merge — see Unresolved Questions.parseFrontmatter() and assertNoDanglingRefs() helpers to scripts/build-dist.mjs
name and description. The dangling-ref guard scans each SKILL.md body line-by-line with the exact pattern /(^|[^\w\/])\/[a-z][a-z0-9-]*:[a-z][a-z0-9-]+\b/ — anchored with (^|[^\w\/]) instead of a leading \b, which would silently fail before the non-word / and miss refs at line start, after whitespace, or inside backticks (the exact forms in the known-bad issue-agent/create-issue fixture) — skipping fenced code blocks and the YAML frontmatter block, and also flags agent-spawn language (lines matching spawn the <name> agent or subagent_type) — throwing with the offending ref so curation rot fails the build instead of shipping broken standalone skills. Scoping the scan this way prevents false positives on references/ paths, YAML keys, and prose colons, where a single false positive would block the whole build.Verify
assertNoDanglingRefs at kit/plugins/issue-agent/skills/create-issue/SKILL.md — it must throw (known slash-command ref). Run it on the three starter skills — all pass. Near-miss check: run it on a skill-reviewer skill whose body contains references/ paths with colons — it must NOT throw (no false positive). parseFrontmatter returns name + description for each starter SKILL.md.buildSkillCatalog() and call it at the end of build()
scripts/skill-catalog.json; for each ref resolve src = kit/plugins/<plugin>/skills/<skill> (throw if SKILL.md is missing); parse frontmatter (throw if name or description is absent); maintain a Map<name, ref> and throw on duplicate skill names (the CLI flattens installs to .claude/skills/<name>/); run the dangling-ref guard; copy the whole src dir to dist/skills/<plugin>/<skill>/, honoring matchesDrop() and reusing copyFileMaybeTransform() so the agentics → agentics-kit URL rewrite stays consistent. Optionally write dist/skills/README.md — a human index of each cataloged skill, its plugin, and description. Runs after the root-files pass.Verify
node scripts/build-dist.mjs creates dist/skills/<plugin>/<skill>/ for each allowlist entry, with SKILL.md plus all support files (references/, assets/, scripts/).checkSkillCatalog() to scripts/build-dist.mjs, called from check()
checkSkillCatalog() function called from check() — keeping plugin and catalog invariants from entangling and making the helper independently testable. It must fail loudly when dist/skills/ is missing, a leaf lacks SKILL.md, a skill name collides, or a DROP pattern leaked in — and it must account for the dist/skills/ path-prefix shape in the existing path-split logic (which currently assumes kit/plugins/<name>/). CI's automated gate is tests/publish/test-skill-catalog.mjs (Step 6); --check remains the local/dev gate and is not wired into the workflow.Verify
node scripts/build-dist.mjs --check passes on a fresh build; deleting a SKILL.md from dist/skills/ (or planting a file matching a DROP pattern) makes it fail. Rebuild to restore.tests/publish/smoke-clean-dist.sh with catalog assertions
dist/skills/ exists and each expected <plugin>/<skill>/SKILL.md is present.Verify
bash tests/publish/smoke-clean-dist.sh passes; temporarily removing a cataloged skill's output dir makes it fail.tests/publish/test-skill-catalog.mjs and wire it into publish-dist.yml
tests/publish/test-dist-transforms.mjs style (pass/fail counter, zero deps) and validates the publish artifact on every run: cataloged dir count matches scripts/skill-catalog.json; every SKILL.md carries name + description; names are globally unique; no body contains a dangling /<plugin>:<command> ref. Runs in .github/workflows/publish-dist.yml after the existing "Test dist transforms" step, against the freshly built dist/.Verify
node tests/publish/test-skill-catalog.mjs exits 0 locally against a fresh build; the new workflow step appears after "Test dist transforms" and the YAML parses.skills CLI" section to README.md
npx skills add shawn-sandy/agentics-kit --skill <name> -a claude-code -y as the primary example, keeping the bare interactive npx skills add shawn-sandy/agentics-kit (agent picker, cross-agent) as the secondary form. Note when to prefer /plugin install instead — wanting a plugin's commands, agents, and hooks rather than a single skill. Author the lines with the dist URL — transformReadmeForDist() already rewrites agentics → agentics-kit, and dist-URL authoring reads correctly in both repos.Verify
README.md; run node scripts/build-dist.mjs and confirm dist/README.md still shows the agentics-kit URLs.CLAUDE.md, .claude/rules/marketplace.md, and CHANGELOG.md
scripts/skill-catalog.json is the curation surface to edit when adding or removing an exported skill. Add a CHANGELOG.md entry under [Unreleased] describing the new channel and its curation surface (Keep-a-Changelog format, matching existing entries). In .claude/rules/marketplace.md, also document the de-publish runbook for a bad publish: remove the entry from scripts/skill-catalog.json, trigger workflow_dispatch on publish-dist.yml, and note the removal in CHANGELOG.md.Verify
scripts/skill-catalog.json; grep skill-catalog.json CLAUDE.md .claude/rules/marketplace.md returns hits in both. CHANGELOG.md carries an [Unreleased] entry for the skill-box channel, and the marketplace rule documents the de-publish runbook.Tests
File: tests/publish/test-skill-catalog.mjs
Type: smoke test
Asserts: after a fresh build, dist/skills/ holds exactly as many <plugin>/<skill> leaf directories as scripts/skill-catalog.json lists — none missing, none extra — each SKILL.md carries name + description, skill names are globally unique, and no body contains a dangling /<plugin>:<command> ref — i.e. the catalog npx skills will consume is real, complete, and standalone-installable.
Run: node scripts/build-dist.mjs && node tests/publish/test-skill-catalog.mjs
File: tests/publish/test-skill-catalog.mjs
Targets: parseFrontmatter() and assertNoDanglingRefs() in scripts/build-dist.mjs, exercised directly against committed fixtures — not the live dist/ output
Key cases: fixture-driven — two stub SKILL.md files under tests/fixtures/skill-catalog/ (one valid with name + description and no slash refs, one invalid containing /plugin:command); the valid fixture parses cleanly; missing keys throw; the invalid fixture throws with the offending ref; committed negative assertion: kit/plugins/issue-agent/skills/create-issue/SKILL.md (known bad) throws — making guard regressions CI-observable; near-miss fixture with references/ colon paths does not throw.
File: tests/publish/smoke-clean-dist.sh
Targets: build() + buildSkillCatalog() + check() over a clean dist/ build
Key cases: dist/skills/ present alongside the plugin dirs; every expected <plugin>/<skill>/SKILL.md present with its support files; whole-dir copy verified — dist/skills/memory-tools/agentic-memory-doctor/references/ exists and is non-empty; no DROP patterns leaked into dist/skills/; failure smoke — delete one dist/skills/.../SKILL.md, run node scripts/build-dist.mjs --check, assert non-zero exit, then rebuild.
File: manual check — no committed file (runs against the published agentics-kit repo)
Targets: published catalog → npx skills CLI → Claude Code install
Key cases: in a scratch dir, npx skills add shawn-sandy/agentics-kit --skill agentic-memory-doctor -a claude-code -y lands the skill in .claude/skills/ with its supporting files intact, and the skill auto-activates in a fresh Claude Code session.
Acceptance Criteria
Verification
- Before implementation: confirm the
npx skillsCLI discovers the two-levelskills/<plugin>/<skill>/catalog layout — read the CLI source or runnpx skills addagainst a scratch repo carrying both layouts. If only flatskills/<name>/is supported, flatten the catalog todist/skills/<skill-name>/and adjust Steps 3–6 before coding. node scripts/build-dist.mjs— confirmdist/skills/<plugin>/<skill>/is created for each allowlist entry, withSKILL.mdand all support files.node scripts/build-dist.mjs --check— passes (catalog present, names unique, no DROP leaks).bash tests/publish/smoke-clean-dist.sh— passes with the new catalog assertions.node tests/publish/test-skill-catalog.mjs— passes.- Negative checks: temporarily add a non-existent skill, a duplicate name, and a skill with a
/plugin:commandref toskill-catalog.json; confirm the build throws on each, then revert. - Manual end-to-end (optional, post-publish): in a scratch dir,
npx skills add shawn-sandy/agentics-kit --skill agentic-memory-doctor -a claude-code -y; confirm it lands in.claude/skills/and that the skill auto-activates in a fresh Claude Code session when a matching request is made.
Completion Checklist
Completion Report
No items to report — all requirements met.
Unresolved Questions
-
Starter shortlist — confirm which skills ship first
In the agentics repo, scripts/skill-catalog.json lists the skills exported standalone to dist/skills/ for npx skills installs. Audit the candidate set and confirm the final starter shortlist. A skill is standalone-safe only if its SKILL.md (and supporting files) never invokes a /<plugin>:<command> slash command, never spawns a plugin agent, and needs no plugin hook. Verify: memory-tools/agentic-memory-doctor, memory-tools/path-rules-advisor, wcag-compliance-reviewer/wcag-compliance-reviewer (expected safe); skill-reviewer/* and code-testing-agent/reviewing-tests (verify, then add); issue-agent/create-issue (expected excluded — references a slash command). Report a table of skill, safe/unsafe verdict, and evidence line, then update scripts/skill-catalog.json with the confirmed list.
Team Review (2026-06-12 15:34:23 UTC)
Executive Summary
Verdict: sound with revisions — apply and proceed. Five core reviewers (architecture, completeness, testability, risk, conventions; no UI signals, so UX/accessibility were not spawned) found the plan architecturally clean: it extends build() rather than adding a pipeline, honors the zero-dependency constraint, and respects the source → dist → publish layering. No reviewer recommended rejection. Two findings dominated: the dangling-ref guard was underspecified (flagged independently by three reviewers — a naïve /word:word/ pattern false-positives on references/ paths, YAML keys, and prose, and one false positive blocks the build), and the npx skills CLI's discovery of the two-level skills/<plugin>/<skill>/ layout was an unverified third-party assumption carrying silent-failure risk for every install.
Role-by-Role Findings
Architecture — Fit is clean; concerns: dangling-ref regex false-positive risk (high), check() growing in two directions without a seam (medium), allowed-tools cross-agent behavioral gap (medium), DROP-pattern integration for skill copies underspecified (medium), dual-inventory scaling risk (low), manual-only E2E (low). Recommended the checkSkillCatalog() factoring and a precisely scoped regex.
Completeness — Steps well-sequenced; gaps: no CHANGELOG.md step (medium), "verify, then add" candidates never resolved into an action (medium), regex spec underdefined including "agent-spawn language" (medium), optional dist/skills/README.md unverifiable (low), Step 8 grep ambiguity (low).
Testability — Solid structural coverage; gaps: unit tests not isolated — they ran against live dist/ (high), guard's negative path not CI-committed (high), no whole-dir copy assertion despite AC1 (medium), no entry-count parity so a partial build could pass (medium), check() failure modes manual-only (medium), manual E2E acceptable but noted (low).
Risk — Overall level: medium. Key risks: unverified CLI layout discovery (high), dangling-ref false positives (medium), no rollback runbook for a bad publish (medium), latent name-collision outside catalog scope (medium), non-atomic local dist/ wipe-and-rebuild (medium, CI-safe — local-publish only), copyFileMaybeTransform() URL rewrite mutating SKILL.md teaching content (low), workflow cancel-in-progress publish gap (low, pre-existing).
Conventions — Strong fit: camelCase helpers, kebab-case files, correct test directory and naming. Issues: skill-catalog.json as the first data file in executables-only scripts/ (medium — suggested .claude-plugin/), unit + objective tests sharing one file vs one-concern-per-file (medium — converged with testability on folding into test-skill-catalog.mjs with a clear section), optional catalog README inconsistent with unconditional root README (low), --check CI wiring unstated (low).
Agreements & Conflicts
- Confirmed (3 reviewers): the dangling-ref regex must be precisely specified with fenced-code and frontmatter exclusions — architecture, completeness, and risk all flagged false-positive blocking.
- Confirmed (2 reviewers): resolve the optional
dist/skills/README.mdambiguity (conventions: make unconditional; completeness: promote or delete). Triage outcome: rejected — kept as "optionally". - Converged (2 reviewers): fold unit assertions into
test-skill-catalog.mjswith fixtures rather than adding a third test file. - Conflict: conventions recommended moving the allowlist to
.claude-plugin/skill-catalog.json; the source plan lockedscripts/skill-catalog.jsonas a do-not-re-litigate decision. Triage outcome: rejected — locked decision honored; placement staysscripts/.
Highest-Risk Issues (priority order)
- Unverified
npx skillsCLI layout discovery — risk, high. Addressed: pre-implementation verification item + acceptance criterion ac7. - Underspecified dangling-ref regex — architecture high, 3× confirmed. Addressed: exact pattern, scan scoping, near-miss verify in Step 2.
- Guard regression invisible to CI — testability, high. Addressed: fixture-driven unit card with committed negative assertion.
- No rollback path for a bad publish — risk, medium. Addressed: de-publish runbook added to Step 8.
- Entry-count parity untested — testability, medium. Addressed: exact-parity assertion in the objective test.
Triage Outcome
Walkthrough ran interactively; 13 findings triaged.
Accepted (11, applied as-is):
- Step 2 why/verify — exact regex spec, scan scoping, agent-spawn keywords, near-miss check (Architecture + Completeness + Risk)
- Verification item 1 + criterion ac7 — pre-implementation CLI layout confirmation with flat-layout fallback (Risk)
- Unit test card — fixture-driven assertions under
tests/fixtures/skill-catalog/+ committed issue-agent negative test (Testability) - Objective test card — exact entry-count parity assertion (Testability)
- Integration test card — whole-dir copy assertion +
--checkfailure smoke (Testability) - Step 4 —
checkSkillCatalog()factoring, path-prefix note, CI vs--checkclarification (Architecture + Conventions) - Step 8 + Files — CHANGELOG.md
[Unreleased]entry (Completeness) - Step 1 why/verify — deterministic verify-then-add gate with
excludedmap (Completeness) - Step 8 why — de-publish runbook in marketplace.md (Risk)
- Unresolved prompt —
allowed-toolscross-agent tool-gating flag in the audit (Architecture) - Next Steps — repo-wide skill-name uniqueness guard follow-up (Risk)
Modified: none.
Rejected (2, recorded only):
- Step 3 — make
dist/skills/README.mdunconditional + URL-rewrite caution (Conventions + Completeness + Risk): developer kept the original "optionally write" wording. - Move
skill-catalog.jsonto.claude-plugin/(Conventions): conflicts with the source plan's locked "do not re-litigate" placement decision;scripts/retained.
Noted without edits
- Local
dist/non-atomicity: a thrownbuildSkillCatalog()leaves a catalog-less localdist/; CI fails safely before publish, so impact is limited to manual local publishes. - Workflow
cancel-in-progresspublish gap is pre-existing behavior; the catalog pass slightly widens the window.
Revised Plan
This document is the revised plan — all accepted edits above were applied in place on 2026-06-12. The plan now carries 8 steps, 7 acceptance criteria, a 7-item end-to-end verification list, and hardened test cards.
Post-review refinement (2026-06-12): at the developer's direction the plan was refocused on Claude Code users as the primary audience (distribution mechanism unchanged). The accepted cross-agent allowed-tools audit edit was subsequently removed as moot for a Claude-first audience; the objective, context, README guidance, pipeline install node, E2E test, and manual verification were reworded accordingly, and a Claude Code auto-activation check was added. A Codex review on PR #316 then corrected the guard regex anchor: the originally specified leading \b cannot match before the non-word /, so refs at line start or after whitespace/backticks would have slipped through; the spec now anchors with (^|[^\w\/]).