Skill Scoping¶
Skills can be scoped so that Claude Code only auto-loads them in the repos where they make sense. This guide explains the scoping model, how enforcement works, and how to author, audit, and migrate skills.
1. Why Scoping?¶
Without scoping, every globally-installed skill loads in every project repo. This causes two problems:
Cross-product contamination. A skill written for VisiFair — for example, vt-c-messegelaende-cleanup — will appear as a suggestion in a VisiMatch project where it has no meaning. In the worst case, Claude auto-invokes it on patterns that superficially match but belong to a different domain.
Cognitive noise. A toolkit with 60+ skills where 30 are irrelevant to the current project increases mental load and makes the relevant skills harder to find.
The real trigger was a practical observation: Niclas noticed that skills involving Silverstripe (a VisiTrans website CMS) were showing up in suggestions while working on VisiMatch container-logistics work. The domains don't share anything — the intrusion was pure noise.
2. The Two-Level Model¶
Skills have one of three effective scopes:
| Scope | Meaning | Enforced by |
|---|---|---|
global |
Available in all repos (default) | Symlink in ~/.claude/skills/ |
product:[Name, ...] |
Available only in repos matching the product path pattern | paths: block in SKILL.md |
| repo-local | Available only in the repo it lives in (.claude/skills/) |
File location |
scope: global (explicit or implied by absence of the field): the skill is installed in ~/.claude/skills/ and Claude Code loads it everywhere.
scope: product:[Name, ...]: the skill has a paths: block in its SKILL.md that restricts Claude Code's auto-matching to repo paths that match the product's glob patterns (e.g., **/04-VisiMatch/**). The paths are generated automatically from intake/projects.yaml.
Repo-local skills (in <repo>/.claude/skills/): there is no scope: repo field — the location itself provides the isolation. Claude Code loads .claude/skills/ relative to the project root.
Missing scope: field¶
If a SKILL.md has no scope: field, it defaults to global with a load-time warning:
⚠ vt-c-my-skill: missing scope: field — defaulting to global.
Add scope: global or scope: product:[...] to suppress this warning.
3. How Enforcement Works¶
scope: is the human-readable annotation. paths: is the enforcement mechanism.
SKILL.md frontmatter
┌─────────────────────────────────────────────────┐
│ scope: product:[VisiMatch] ← you edit this │
│ │
│ # AUTO-GENERATED — do not edit by hand │
│ paths: ← this is built │
│ - "**/04-VisiMatch/**" from scope: │
└─────────────────────────────────────────────────┘
│
│ Claude Code reads paths: at load time
▼
Matches if cwd is under .../04-VisiMatch/...
The paths: block is machine-generated by bin/regenerate-skill-paths.sh. It reads the products: registry in intake/projects.yaml, which maps each product name to its directory glob patterns:
# intake/projects.yaml
products:
VisiMatch:
paths: ["**/04-VisiMatch/**"]
VisiFair:
paths: ["**/04-VisiFair-*/**", "**/03-VisiTrans/V004-*-VisiFair*/**"]
VisiTrans:
paths: ["**/03-VisiTrans/**"]
VisiArea:
paths: ["**/04-VisiArea-*/**"]
The full pipeline¶
scope: product:[VisiMatch]
│
▼
regenerate-skill-paths.sh --write
│ reads products: from intake/projects.yaml
│ computes union of path patterns
▼
paths:
- "**/04-VisiMatch/**" ← written to SKILL.md
│
▼
Claude Code loads SKILL.md
→ matches if cwd matches any paths: glob
→ skill auto-activates in VisiMatch repos
Drift protection¶
The paths: block must stay in sync with scope:. Three mechanisms catch drift:
-
Pre-commit hook (
hooks/pre-commit-skill-paths-check): blocks any commit where a product-scoped skill'spaths:block doesn't match what--checkmode would generate. Hard-fail — no warnings, no override. -
setup.sh --verify: runs at install/update time; fails with a report of all drifted skills. -
vt-c-toolkit-updateStep 6.5: runs--checkafter everygit pull; refuses to record the new SHA until drift is resolved.
If you see a drift error, the fix is always the same:
bash plugins/core-standards/bin/regenerate-skill-paths.sh \
--write plugins/core-standards/skills/<slug>/SKILL.md \
--projects intake/projects.yaml
git add plugins/core-standards/skills/<slug>/SKILL.md
git commit -m "chore: regenerate paths block for <slug>"
4. Authoring a Product-Scoped Skill¶
A minimal VisiMatch-scoped skill:
---
name: vt-c-my-visimatch-skill
description: Does something specific to container logistics in VisiMatch.
scope: product:[VisiMatch]
# AUTO-GENERATED by regenerate-skill-paths.sh — do not edit by hand
paths:
- "**/04-VisiMatch/**"
---
# My VisiMatch Skill
...
You write the frontmatter down to and including scope:. The paths: block is generated by the regen script — the AUTO-GENERATED marker is the boundary.
Steps to add a new product-scoped skill:
- Create
plugins/core-standards/skills/<slug>/SKILL.mdwithname:andscope: product:[X]. - Run:
- Add the symlink entry to
plugins/core-standards/.claude-plugin/skill-symlinks.manifest: - Run
scripts/setup.sh --update(or--fullfor a fresh install) to deploy the symlink. - Verify:
ls ~/.claude/skills/vt-c-<slug>— symlink should exist.
Note: For product-scoped skills, the
paths:block in SKILL.md suppresses auto-loading outside matching repos. But the symlink in~/.claude/skills/still exists. You can always invoke a product-scoped skill manually with/vt-c-<slug>from any repo — scoping only suppresses automatic context-loading.
5. Multi-Product Skills¶
Some skills serve multiple products. Use a comma-separated list:
The regen script produces the union of all products' path patterns:
# AUTO-GENERATED by regenerate-skill-paths.sh — do not edit by hand
paths:
- "**/03-VisiTrans/**"
- "**/04-VisiMatch/**"
- "**/04-VisiFair-*/**"
- "**/03-VisiTrans/V004-*-VisiFair*/**"
- "**/04-VisiArea-*/**"
Real example: vt-c-visitrans-cd — the Corporate Design skill applies to all four VisiTrans products, so it gets all four product path sets.
When to use multi-product scope vs global:
- Multi-product when the skill is product-relevant but not general-purpose (corporate design assets, brand guidelines — not used in non-VisiTrans work).
- Global when the skill is universally useful (workflow steps like
/vt-c-3-build, utility skills like/vt-c-scaffold).
6. Promote and Demote Workflow¶
Promote: repo-local → global toolkit¶
A skill you wrote inside a project repo can be promoted to the global toolkit:
bash plugins/core-standards/skills/skill-promote/scripts/promote.sh \
~/path/to/project/.claude/skills/<slug>/SKILL.md
The script:
1. Copies the skill directory to plugins/core-standards/skills/<slug>/
2. Sets scope: global (overrides any existing scope)
3. Regenerates paths (empty for global)
4. Adds the manifest entry
5. Creates ~/.claude/skills/vt-c-<slug> symlink
6. Commits
Or use the skill directly: /vt-c-skill-promote <source-SKILL.md>
Demote: global → product scope¶
A globally-installed skill can be restricted to a specific product:
bash plugins/core-standards/skills/skill-demote/scripts/demote.sh \
vt-c-<skill-name> --to-product VisiMatch
The script:
1. Updates scope: product:[VisiMatch]
2. Regenerates the paths block
3. Removes ~/.claude/skills/vt-c-<skill-name> symlink
4. Removes the manifest entry
5. Commits
After demotion, the skill is only auto-loaded in VisiMatch repos. You can still invoke it manually with /vt-c-<skill-name> from any repo — the symlink removal only affects auto-loading.
Or use the skill directly: /vt-c-skill-demote vt-c-<skill-name> --to-product <ProductName>
7. Migration and Audit¶
Run /vt-c-toolkit-review to get a scope health report alongside the usual proposal review:
Cross-product warning — if the current repo's product is known and a skill's scope: product:[X] doesn't include it:
⚠ Skill vt-c-container-logistics-ux-expert scoped to {VisiMatch} but current repo
is product=VisiFair. Skill is path-blocked; manual /vt-c-container-logistics-ux-expert
still works.
Heuristic suggestion — skills with scope: global that contain product-signal keywords:
suggest: vt-c-messegelaende-cleanup matches VisiFair signals (keyword: messegelaende)
→ consider scope: product:[VisiFair]
These warnings are advisory — they don't fail the review. Act on them at your discretion.
First-wave audit¶
These four skills have already been scoped in the initial audit (SPEC-123 C12):
| Skill | Scope |
|---|---|
vt-c-messegelaende-cleanup |
product:[VisiFair] |
vt-c-skill-venue-hall-research |
product:[VisiFair] |
vt-c-visitrans-cd |
product:[VisiTrans, VisiMatch, VisiFair, VisiArea] |
vt-c-container-logistics-ux-expert |
product:[VisiMatch] |
All other skills remain scope: global (the safe default) until explicitly audited.
8. What Claude Code's paths: Actually Does¶
Claude Code reads the paths: field from a skill's SKILL.md at session start. Each entry is a glob pattern matched against the current working directory path.
If the cwd matches any pattern (e.g., /Users/rolf/01-repositories/04-VisiMatch/C038-VisiMatch), the skill is included in the session context. If no pattern matches, the skill is excluded from auto-loading.
The ** glob segments match any number of path components. A pattern like **/04-VisiMatch/** matches any directory that has 04-VisiMatch as a component in its path, regardless of depth.
skillOverrides (per-user config): you can override loading behavior per-skill in your ~/.claude/settings.json. This is per-user and not in version control — use it only for personal exceptions. The spec-level mechanism is the paths: field in SKILL.md.
9. Limitations¶
Scoping is not security isolation. A developer can always invoke a product-scoped skill manually with /vt-c-<name> from any repo. The paths: mechanism only suppresses automatic context inclusion — it does not prevent intentional use.
skillOverrides is per-user. Users can override skill loading in their own ~/.claude/settings.json. There is no enforcement across all users.
Paths are literal globs, not semantic product detection. If a repo lives outside the expected directory structure (e.g., not in 04-VisiMatch/), the path pattern won't match even if the repo is a VisiMatch product. The patterns are intentionally convention-based — they work because the VisiTrans repo naming convention is consistent.
Repo-local skills have no scope: field. They're scoped by location, not annotation. Don't add scope: repo — the field is not defined; use scope: global or omit it (same effect when the skill lives in .claude/skills/).
10. FAQ¶
Q: Does scope: product:[X] provide security isolation?
No. It suppresses automatic context loading. Any user can still type /vt-c-<skill> to invoke it manually in any repo. Use scoping to reduce noise and improve relevance — not as an access control mechanism.
Q: What if a skill is useful in two products but not globally?
Use the multi-product list syntax: scope: product:[VisiMatch, VisiArea]. The regen script computes the union of both products' path patterns.
Q: I'm in a repo that isn't registered in intake/projects.yaml. Does scoping still work?
Yes. The paths: globs match against the cwd string — they don't require a registry entry. As long as the repo directory follows the VisiTrans naming convention (04-VisiMatch/...), the match works. The registry is only needed for the heuristic audit in /vt-c-toolkit-review.
Q: What happens if I edit the AUTO-GENERATED block by hand?
The pre-commit hook blocks the commit with:
❌ paths: out of sync in my-skill/SKILL.md
Run: plugins/core-standards/bin/regenerate-skill-paths.sh --write ...
Q: How do I add a new product to the registry?
Add a new entry to the products: section of intake/projects.yaml:
regenerate-skill-paths.sh --write on any skill you want to scope to it.
Q: I promoted a skill. Should I also run setup.sh --full?
No — /vt-c-skill-promote creates the symlink directly. setup.sh --full is only needed when installing the toolkit fresh or when reconciling the manifest after a manual change.