Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8e9742e

Browse files
committed
feat(claudeskill): add Claude Skill command support#452
Introduce ClaudeSkillCommand for dynamic agent skills via SKILL.md. Refactor frontmatter parsing to SkillFrontmatter. Enable /skill.* commands and execution in DevIns. Add related tests.
1 parent1b9ec3c commit8e9742e

File tree

10 files changed

+442
-45
lines changed

10 files changed

+442
-45
lines changed

‎core/src/main/kotlin/cc/unitmesh/devti/command/dataprovider/BuiltinCommand.kt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ enum class BuiltinCommand(
245245
true,
246246
enableInSketch=true
247247
),
248+
CLAUDE_SKILL(
249+
"skill",
250+
"Execute Claude Skills for specialized agent capabilities. Skills are organized folders of instructions, scripts, and resources that agents can discover and load dynamically. Supports subcommands like /skill.pdf, /skill.algorithmic-art, etc. Loads skills from project directories containing SKILL.md files or ~/.claude/skills/ directory.",
251+
AutoDevIcons.IDEA,
252+
true,
253+
true,
254+
enableInSketch=true
255+
),
248256
;
249257

250258
companionobject {
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
packagecc.unitmesh.devti.command.dataprovider
2+
3+
importcc.unitmesh.devti.AutoDevIcons
4+
importcom.intellij.openapi.project.Project
5+
importjava.nio.file.Path
6+
importjavax.swing.Icon
7+
importkotlin.io.path.exists
8+
importkotlin.io.path.isDirectory
9+
importkotlin.io.path.listDirectoryEntries
10+
importkotlin.io.path.readText
11+
12+
/**
13+
* ClaudeSkillCommand represents a Claude Skill loaded from directories containing SKILL.md files.
14+
*
15+
* Claude Skills are organized folders of instructions, scripts, and resources that agents can
16+
* discover and load dynamically. Each skill is a directory containing a SKILL.md file with
17+
* YAML frontmatter (name, description) and markdown content.
18+
*
19+
* Skills can be located in:
20+
* - Project root directories containing SKILL.md
21+
* - ~/.claude/skills/ directory (user-level skills)
22+
*
23+
* Example usage:
24+
* ```
25+
* /skill.pdf <arguments>
26+
* ```
27+
*/
28+
data classClaudeSkillCommand(
29+
valskillName:String,
30+
valdescription:String,
31+
valtemplate:String,
32+
valskillPath:Path,
33+
valicon:Icon =AutoDevIcons.IDEA
34+
) {
35+
val fullCommandName:String
36+
get()="skill.$skillName"
37+
38+
/**
39+
* Execute the command using SkillTemplateCompiler for proper variable resolution.
40+
* This method:
41+
* 1. Parses frontmatter to extract variable definitions
42+
* 2. Resolves variables (e.g., loading file contents)
43+
* 3. Uses Velocity template engine for compilation
44+
*
45+
* @param project The current project
46+
* @param arguments User-provided arguments
47+
* @return Compiled template with all variables resolved
48+
*/
49+
funexecuteWithCompiler(project:Project,arguments:String):String {
50+
val compiler=SpecKitTemplateCompiler(project, template, arguments)
51+
return compiler.compile()
52+
}
53+
54+
/**
55+
* Convert to CustomCommand for compatibility with existing DevIns infrastructure
56+
*/
57+
funtoCustomCommand():CustomCommand {
58+
returnCustomCommand(
59+
commandName= fullCommandName,
60+
content= description,
61+
icon= icon
62+
)
63+
}
64+
65+
companionobject {
66+
privateconstvalSKILL_FILE="SKILL.md"
67+
privateconstvalUSER_SKILLS_DIR=".claude/skills"
68+
69+
/**
70+
* Load all Claude Skills from available locations:
71+
* 1. Project root directories containing SKILL.md
72+
* 2. User home ~/.claude/skills/ directory
73+
*/
74+
funall(project:Project):List<ClaudeSkillCommand> {
75+
val skills= mutableListOf<ClaudeSkillCommand>()
76+
77+
// Load from project root
78+
skills.addAll(loadFromProjectRoot(project))
79+
80+
// Load from user skills directory
81+
skills.addAll(loadFromUserSkillsDir())
82+
83+
return skills
84+
}
85+
86+
/**
87+
* Load skills from project root directories
88+
*/
89+
privatefunloadFromProjectRoot(project:Project):List<ClaudeSkillCommand> {
90+
val projectPath= project.basePath?:return emptyList()
91+
val projectRoot=Path.of(projectPath)
92+
93+
if (!projectRoot.exists()) {
94+
return emptyList()
95+
}
96+
97+
returntry {
98+
projectRoot.listDirectoryEntries()
99+
.filter { it.isDirectory() }
100+
.mapNotNull { dir->
101+
val skillFile= dir.resolve(SKILL_FILE)
102+
if (skillFile.exists()) {
103+
loadSkillFromFile(skillFile, dir)
104+
}else {
105+
null
106+
}
107+
}
108+
}catch (e:Exception) {
109+
emptyList()
110+
}
111+
}
112+
113+
/**
114+
* Load skills from user's ~/.claude/skills/ directory
115+
*/
116+
privatefunloadFromUserSkillsDir():List<ClaudeSkillCommand> {
117+
val userHome=System.getProperty("user.home")?:return emptyList()
118+
val skillsDir=Path.of(userHome,USER_SKILLS_DIR)
119+
120+
if (!skillsDir.exists()) {
121+
return emptyList()
122+
}
123+
124+
returntry {
125+
skillsDir.listDirectoryEntries()
126+
.filter { it.isDirectory() }
127+
.mapNotNull { dir->
128+
val skillFile= dir.resolve(SKILL_FILE)
129+
if (skillFile.exists()) {
130+
loadSkillFromFile(skillFile, dir)
131+
}else {
132+
null
133+
}
134+
}
135+
}catch (e:Exception) {
136+
emptyList()
137+
}
138+
}
139+
140+
/**
141+
* Load a skill from a SKILL.md file
142+
*/
143+
privatefunloadSkillFromFile(skillFile:Path,skillDir:Path):ClaudeSkillCommand? {
144+
returntry {
145+
val template= skillFile.readText()
146+
val (frontmatter, _)=SkillFrontmatter.parse(template)
147+
148+
// Extract skill name from frontmatter or directory name
149+
val skillName= frontmatter?.name?.ifEmpty {null }
150+
?: skillDir.fileName.toString()
151+
152+
// Extract description from frontmatter
153+
val description= frontmatter?.description?.ifEmpty {null }
154+
?:"Claude Skill:$skillName"
155+
156+
ClaudeSkillCommand(
157+
skillName= skillName,
158+
description= description,
159+
template= template,
160+
skillPath= skillDir
161+
)
162+
}catch (e:Exception) {
163+
null
164+
}
165+
}
166+
167+
/**
168+
* Find a specific Claude Skill by skill name
169+
*/
170+
funfromSkillName(project:Project,skillName:String):ClaudeSkillCommand? {
171+
return all(project).find { it.skillName== skillName }
172+
}
173+
174+
/**
175+
* Find a specific Claude Skill by full command name (e.g., "skill.pdf")
176+
*/
177+
funfromFullName(project:Project,commandName:String):ClaudeSkillCommand? {
178+
return all(project).find { it.fullCommandName== commandName }
179+
}
180+
181+
/**
182+
* Check if Claude Skills are available in the project
183+
*/
184+
funisAvailable(project:Project):Boolean {
185+
return all(project).isNotEmpty()
186+
}
187+
}
188+
}
189+
Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,40 @@
11
packagecc.unitmesh.devti.command.dataprovider
22

33
/**
4-
* Represents the frontmatter section of a SpecKitprompt file.
5-
*
6-
*SpecKit promptfiles (e.g., speckit.plan.prompt.md) can contain YAML frontmatter
4+
* Represents the frontmatter section of askill file (SpecKitor Claude Skills).
5+
*
6+
*Skillfiles (e.g., speckit.plan.prompt.md or SKILL.md) can contain YAML frontmatter
77
* with metadata and variable definitions.
8-
*
8+
*
99
* Example:
1010
* ```yaml
1111
* ---
12+
* name: PDF Skill
1213
* description: Execute the implementation planning workflow
1314
* variables:
1415
* FEATURE_SPEC: "path/to/spec.md"
1516
* IMPL_PLAN: "path/to/plan.md"
1617
* ---
1718
* ```
1819
*/
19-
data classSpecKitFrontmatter(
20+
data classSkillFrontmatter(
2021
/**
21-
* Description of what this SpecKit command does
22+
* Name of the skill (used for Claude Skills)
23+
*/
24+
valname:String ="",
25+
26+
/**
27+
* Description of what this skill does
2228
*/
2329
valdescription:String ="",
24-
30+
2531
/**
2632
* Variable definitions that can be used in the template.
2733
* Key: variable name (e.g., "FEATURE_SPEC")
2834
* Value: variable value or path to load content from
2935
*/
3036
valvariables:Map<String,Any> = emptyMap(),
31-
37+
3238
/**
3339
* Any additional frontmatter fields not explicitly defined above
3440
*/
@@ -39,17 +45,17 @@ data class SpecKitFrontmatter(
3945
* Parse frontmatter from a markdown string.
4046
* Returns null if no frontmatter is found.
4147
*/
42-
funparse(markdown:String):Pair<SpecKitFrontmatter?,String> {
48+
funparse(markdown:String):Pair<SkillFrontmatter?,String> {
4349
val frontmatterRegex=Regex("^---\\s*\\n(.*?)\\n---\\s*\\n",RegexOption.DOT_MATCHES_ALL)
4450
val match= frontmatterRegex.find(markdown)
45-
51+
4652
if (match==null) {
4753
returnPair(null, markdown)
4854
}
49-
55+
5056
val yamlContent= match.groupValues[1]
5157
val contentWithoutFrontmatter= markdown.substring(match.range.last+1)
52-
58+
5359
returntry {
5460
val frontmatter= parseYaml(yamlContent)
5561
Pair(frontmatter, contentWithoutFrontmatter)
@@ -58,23 +64,25 @@ data class SpecKitFrontmatter(
5864
Pair(null, contentWithoutFrontmatter)
5965
}
6066
}
61-
67+
6268
/**
63-
* Parse YAML content intoSpecKitFrontmatter
69+
* Parse YAML content intoSkillFrontmatter
6470
*/
65-
privatefunparseYaml(yamlContent:String):SpecKitFrontmatter {
71+
privatefunparseYaml(yamlContent:String):SkillFrontmatter {
6672
val yaml= org.yaml.snakeyaml.Yaml()
6773
val data= yaml.load<Map<String,Any>>(yamlContent)?: emptyMap()
68-
74+
75+
val name= data["name"]?.toString()?:""
6976
val description= data["description"]?.toString()?:""
70-
77+
7178
@Suppress("UNCHECKED_CAST")
7279
val variables= (data["variables"]as?Map<String,Any>)?: emptyMap()
73-
80+
7481
// Collect any additional fields
75-
val additionalFields= data.filterKeys { it!inlistOf("description","variables") }
76-
77-
returnSpecKitFrontmatter(
82+
val additionalFields= data.filterKeys { it!inlistOf("name","description","variables") }
83+
84+
returnSkillFrontmatter(
85+
name= name,
7886
description= description,
7987
variables= variables,
8088
additionalFields= additionalFields
@@ -83,3 +91,9 @@ data class SpecKitFrontmatter(
8391
}
8492
}
8593

94+
/**
95+
* Type alias for backward compatibility with existing SpecKit code
96+
*/
97+
@Deprecated("Use SkillFrontmatter instead",ReplaceWith("SkillFrontmatter"))
98+
typealiasSpecKitFrontmatter=SkillFrontmatter
99+

‎core/src/main/kotlin/cc/unitmesh/devti/command/dataprovider/SpecKitTemplateCompiler.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ class SpecKitTemplateCompiler(
3333

3434
/**
3535
* Compile the template with variable substitution.
36-
*
36+
*
3737
* @return The compiled template string with all variables resolved
3838
*/
3939
funcompile():String {
4040
// 1. Parse frontmatter and content
41-
val (frontmatter, content)=SpecKitFrontmatter.parse(template)
41+
val (frontmatter, content)=SkillFrontmatter.parse(template)
4242

4343
// 2. Set basic variables
4444
velocityContext.put("ARGUMENTS", arguments)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp