Skip to content

feat(claude plugin build): config-driven exclude for the plugin tree-copy (drafts excluded from skill packaging still ship in dist) #123

Description

@jdutton

Problem

The claude-phase plugin tree-copy in vat build is verbatim. packages/cli/src/commands/claude/plugin/tree-copy.ts copies everything under the plugin source: dir into the dist plugin tree with only a hardcoded exclusion plus gitignore:

const EXCLUDE_PATTERNS = ['.claude-plugin/**'];
...
const files = await crawlDirectory({
    baseDir: sourceDir,
    include: ['**/*'],
    exclude: EXCLUDE_PATTERNS,
    ...
    respectGitignore: true,
    ...
});

There is no config-driven exclude for this phase.

Consequence: a draft / work-in-progress skill that is deliberately excluded from skill packaging — via skills.exclude globs and omission from the marketplace plugin's explicit skills: allowlist — is still copied wholesale into dist/.claude/plugins/marketplaces/<mp>/plugins/<plugin>/skills/<draft-skill>/. That dist tree is what gets published, and skills under a plugin's skills/ directory are auto-discoverable by the consuming runtime, so the draft ships and becomes invocable despite being excluded everywhere the config currently allows.

It also breaks the verify gate: vat verify --only marketplace validates the dist marketplace tree (packages/cli/src/commands/verify.ts), so validation errors in the intentionally-unfinished draft skill fail the gate even when every shipped skill is clean.

Why drafts live in the plugin tree at all: when a plugin is co-designed in its source repo (non-developer contributors reviewing and editing skills in place), in-progress skills need to live next to shipped ones on the same branch — reviewable in place, but must not ship. The files are tracked source, so .gitignore is not an option (and the tree-copy's respectGitignore rightly wouldn't be the place for real source anyway).

Proposed feature

Either (in preference order):

  1. Have the claude plugin tree-copy honor the existing skills.exclude globs (and/or the per-plugin explicit skills: allowlist), so anything intentionally excluded from skill packaging also stays out of the copied plugin tree in dist; or
  2. A dedicated tree-copy exclude list in config, e.g.:
claude:
  marketplaces:
    <mp>:
      plugins:
        - name: <plugin>
          source: <dir>
          exclude:
            - "skills/<draft-skill>/**"

Precedent: the skills phase already supports both skills.exclude (consumed in packages/cli/src/commands/skills/skill-discovery.ts, which reads skills.include/exclude from vibe-agent-toolkit.config.yaml) and the per-plugin explicit skills: list. The claude tree-copy is the only packaging surface without an equivalent exclusion mechanism, so it silently reintroduces exactly what the other phases removed.

Workaround in use

Temporary build-side prune: a small zero-dep post-build script deletes dist/**/skills/<draft-skill>/ before the verify gate / publish runs. It works, but it is machinery every project with a draft skill under co-design will have to reinvent.

Observed on vat 0.1.35 (macOS, npm global install).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions