MCPcopy
hub / github.com/codeaashu/claude-code / getClaudeSkillScope

Function getClaudeSkillScope

src/utils/permissions/filesystem.ts:101–157  ·  view source on GitHub ↗
(
  filePath: string,
)

Source from the content-addressed store, hash-verified

99 * require granting session access to all of .claude/ (settings.json, hooks/, etc.).
100 */
101export function getClaudeSkillScope(
102 filePath: string,
103): { skillName: string; pattern: string } | null {
104 const absolutePath = expandPath(filePath)
105 const absolutePathLower = normalizeCaseForComparison(absolutePath)
106
107 const bases = [
108 {
109 dir: expandPath(join(getOriginalCwd(), '.claude', 'skills')),
110 prefix: '/.claude/skills/',
111 },
112 {
113 dir: expandPath(join(homedir(), '.claude', 'skills')),
114 prefix: '~/.claude/skills/',
115 },
116 ]
117
118 for (const { dir, prefix } of bases) {
119 const dirLower = normalizeCaseForComparison(dir)
120 // Try both path separators (Windows paths may not be normalized to /)
121 for (const s of [sep, '/']) {
122 if (absolutePathLower.startsWith(dirLower + s.toLowerCase())) {
123 // Match on lowercase, but slice the ORIGINAL path so the skill name
124 // preserves case (pattern matching downstream is case-sensitive)
125 const rest = absolutePath.slice(dir.length + s.length)
126 const slash = rest.indexOf('/')
127 const bslash = sep === '\\' ? rest.indexOf('\\') : -1
128 const cut =
129 slash === -1
130 ? bslash
131 : bslash === -1
132 ? slash
133 : Math.min(slash, bslash)
134 // Require a separator: file must be INSIDE the skill dir, not a
135 // file directly under skills/ (no skill scope for that)
136 if (cut <= 0) return null
137 const skillName = rest.slice(0, cut)
138 // Reject traversal and empty. Use includes('..') not === '..' to
139 // match step 1.6's ruleContent.includes('..') guard: a skillName like
140 // 'v2..beta' would otherwise produce a suggestion step 1.7 emits but
141 // step 1.6 always rejects (dead suggestion, infinite re-prompt).
142 if (!skillName || skillName === '.' || skillName.includes('..')) {
143 return null
144 }
145 // Reject glob metacharacters. skillName is interpolated into a
146 // gitignore pattern consumed by ignore().add() in matchingRuleForInput
147 // at step 1.6. A directory literally named '*' (valid on POSIX) would
148 // produce '/.claude/skills/*/**' which matches ALL skills. Return null
149 // to fall through to generateSuggestions() instead.
150 if (/[*?[\]]/.test(skillName)) return null
151 return { skillName, pattern: prefix + skillName + '/**' }
152 }
153 }
154 }
155
156 return null
157}
158

Callers 1

Calls 3

expandPathFunction · 0.85
getOriginalCwdFunction · 0.85

Tested by

no test coverage detected