From 7752234e7518ffc6613ffbcc639b4f6448ca480d Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Fri, 10 Apr 2026 11:57:26 -0700 Subject: [PATCH] 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//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 --- get-shit-done/bin/lib/init.cjs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 0f50fe57c..20a58dfa5 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -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 `\nRead these user-configured skills:\n${lines}\n`; }