Skip to content

feat: separate plugin and skills #311

@christso

Description

@christso

Problem

Currently skills, hooks, and other artifacts need to be uniquely named otherwise links break — when conflicts are detected, a plugin-name or hash prefix is appended to the artifact folder name. This only works for specific marketplaces where they follow unique naming standards.

This is fine for internal plugins since we control the naming. But doesn't work for public plugins like superpowers which have names conflicting with other plugins.

Current Behavior

When plugins are synced, skills are copied to client skill directories (e.g., .claude/skills/, .agents/skills/). The folder name on disk becomes the skill's identity — it's how clients discover and reference skills.

Current dedup logic (src/utils/skill-name-resolver.ts):

  • No conflict → folder name as-is (e.g., coding-standards/)
  • Folder name conflicts across plugins → {plugin}_{skill} (e.g., plugin-a_coding-standards/)
  • Folder AND plugin name conflict → {org}_{plugin}_{skill} (e.g., acme_plugin-a_coding-standards/)

Why this breaks

Dedup only kicks in when there's a conflict. This means the same skill gets different names depending on what other plugins are installed:

  1. User installs superpowers plugin → skill lands as brainstorming/
  2. User installs another plugin that also has brainstorming/ → both get renamed to superpowers_brainstorming/ and other_brainstorming/

Any hardcoded references to brainstorming (in AGENTS.md, other skills, etc.) break silently when step 2 happens.

Proposed Solution: Always-Namespaced Mode

Add a config option to always prefix skill names with the plugin namespace, regardless of conflicts:

# workspace.yaml
skillNamespace: always   # default: "auto" (current behavior)
Mode Behavior
auto (default) Current behavior — only prefix on conflict
always Every skill gets {plugin}_{skill} naming

Implementation Guide

Key files

File Change
src/models/workspace-config.ts Add skillNamespace field to schema
src/utils/skill-name-resolver.ts Add mode param to resolveSkillNames(), always-qualify path
src/core/sync.ts Thread config through buildPluginSkillNameMaps() calls (~lines 1841 and 2220)
tests/unit/utils/skill-name-resolver.test.ts Add always mode tests (existing tests cover current tiers)
docs/src/content/docs/reference/configuration.mdx Document new option
docs/src/content/docs/guides/plugins.mdx Update dedup section

Implementation detail

In src/utils/skill-name-resolver.ts, add a mode parameter to resolveSkillNames(). When always, skip the "no conflict" fast path (line 90-101) and always qualify with plugin name.

Scope boundaries

  • Skills that reference other skills by name in their content (e.g., "run the brainstorming skill first") would still need to use the namespaced name. This is a documentation/convention problem, not a code problem.
  • The {org}_{plugin}_{skill} tier 3 disambiguator is still needed even in always mode if two plugins from different orgs have the same plugin name.

Related

  • PR fix(sync): deduplicate repo skills and rename heading #339 fixed a separate issue: repository skills (discovered from workspace repos, embedded as an index in AGENTS.md) had duplicates. Those now deduplicate by picking a winner (.agents/ priority, then largest file). That is a different mechanism from plugin skill dedup — repo skills are pointers in an index, not copied files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions