feat(config): support global skills from ~/.claude/skills/ in agent_skills (#1992)

Add global: prefix for agent_skills config entries that resolve to
~/.claude/skills/<name>/SKILL.md instead of the project root. This
allows injecting globally-installed skills (e.g., shadcn, supabase)
into GSD sub-agents without duplicating them into every project.

Example config:
  "agent_skills": {
    "gsd-executor": ["global:shadcn", "global:supabase-postgres"]
  }

Security: skill names are validated against /^[a-zA-Z0-9_-]+$/ to
prevent path traversal. The ~/.claude/skills/ directory is a trusted
runtime-controlled location. Project-relative paths continue to use
validatePath() containment checks as before.

Closes #1992

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tibsfox
2026-04-10 11:57:26 -07:00
parent 6c2795598a
commit 7752234e75

View File

@@ -1470,6 +1470,24 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
for (const skillPath of skillPaths) {
if (typeof skillPath !== 'string') continue;
// Support global: prefix for skills installed at ~/.claude/skills/ (#1992)
if (skillPath.startsWith('global:')) {
const skillName = skillPath.slice(7);
// Sanitize: skill name must be alphanumeric, hyphens, or underscores only
if (!/^[a-zA-Z0-9_-]+$/.test(skillName)) {
process.stderr.write(`[agent-skills] WARNING: Invalid global skill name "${skillName}" — skipping\n`);
continue;
}
const globalSkillDir = path.join(require('os').homedir(), '.claude', 'skills', skillName);
const globalSkillMd = path.join(globalSkillDir, 'SKILL.md');
if (!fs.existsSync(globalSkillMd)) {
process.stderr.write(`[agent-skills] WARNING: Global skill not found at "~/.claude/skills/${skillName}/SKILL.md" — skipping\n`);
continue;
}
validPaths.push({ ref: `${globalSkillDir}/SKILL.md`, display: `~/.claude/skills/${skillName}` });
continue;
}
// Validate path safety — must resolve within project root
const pathCheck = validatePath(skillPath, projectRoot);
if (!pathCheck.safe) {
@@ -1484,12 +1502,12 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
continue;
}
validPaths.push(skillPath);
validPaths.push({ ref: `${skillPath}/SKILL.md`, display: skillPath });
}
if (validPaths.length === 0) return '';
const lines = validPaths.map(p => `- @${p}/SKILL.md`).join('\n');
const lines = validPaths.map(p => `- @${p.ref}`).join('\n');
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
}