Skip to content

Commit 9aea095

Browse files
committed
Updated version in skill.md when there is a conflict
1 parent bf1430c commit 9aea095

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

skills/commands/publish/publish.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ func (pc *PublishCommand) Run() error {
114114
return err
115115
}
116116

117+
if meta.Version != "" && meta.Version != version {
118+
if updateErr := UpdateSkillMetaVersion(pc.skillDir, version); updateErr != nil {
119+
return fmt.Errorf("failed to update SKILL.md version: %w", updateErr)
120+
}
121+
log.Info(fmt.Sprintf("Updated SKILL.md version from '%s' to '%s'", meta.Version, version))
122+
}
123+
117124
log.Info(fmt.Sprintf("Publishing skill '%s' version '%s'", slug, version))
118125

119126
zipPath, err := pc.resolveZip(slug, version)

skills/commands/publish/publish_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,114 @@ version: '1.5.0'
105105
assert.Equal(t, "1.5.0", meta.Version)
106106
}
107107

108+
func TestUpdateSkillMetaVersion_ReplacesExisting(t *testing.T) {
109+
dir := t.TempDir()
110+
skillMD := `---
111+
name: my-skill
112+
description: A great skill
113+
version: 1.0.0
114+
---
115+
116+
# My Skill
117+
118+
Body content here.
119+
`
120+
require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(skillMD), 0644))
121+
122+
err := UpdateSkillMetaVersion(dir, "2.0.0")
123+
require.NoError(t, err)
124+
125+
meta, err := ParseSkillMeta(dir)
126+
require.NoError(t, err)
127+
assert.Equal(t, "2.0.0", meta.Version)
128+
assert.Equal(t, "my-skill", meta.Name)
129+
assert.Equal(t, "A great skill", meta.Description)
130+
131+
data, err := os.ReadFile(filepath.Join(dir, "SKILL.md"))
132+
require.NoError(t, err)
133+
assert.Contains(t, string(data), "Body content here.")
134+
}
135+
136+
func TestUpdateSkillMetaVersion_QuotedVersion(t *testing.T) {
137+
dir := t.TempDir()
138+
skillMD := `---
139+
name: quoted-skill
140+
version: "1.0.0"
141+
---
142+
143+
# Quoted
144+
`
145+
require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(skillMD), 0644))
146+
147+
err := UpdateSkillMetaVersion(dir, "3.0.0")
148+
require.NoError(t, err)
149+
150+
meta, err := ParseSkillMeta(dir)
151+
require.NoError(t, err)
152+
assert.Equal(t, "3.0.0", meta.Version)
153+
}
154+
155+
func TestUpdateSkillMetaVersion_SingleQuotedVersion(t *testing.T) {
156+
dir := t.TempDir()
157+
skillMD := `---
158+
name: sq-skill
159+
version: '1.5.0'
160+
---
161+
`
162+
require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(skillMD), 0644))
163+
164+
err := UpdateSkillMetaVersion(dir, "1.6.0")
165+
require.NoError(t, err)
166+
167+
meta, err := ParseSkillMeta(dir)
168+
require.NoError(t, err)
169+
assert.Equal(t, "1.6.0", meta.Version)
170+
}
171+
172+
func TestUpdateSkillMetaVersion_NoVersionField(t *testing.T) {
173+
dir := t.TempDir()
174+
skillMD := `---
175+
name: no-version-skill
176+
description: No version here
177+
---
178+
179+
# Content
180+
`
181+
require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(skillMD), 0644))
182+
183+
err := UpdateSkillMetaVersion(dir, "1.0.0")
184+
require.NoError(t, err)
185+
186+
data, err := os.ReadFile(filepath.Join(dir, "SKILL.md"))
187+
require.NoError(t, err)
188+
assert.Equal(t, skillMD, string(data))
189+
}
190+
191+
func TestUpdateSkillMetaVersion_PreservesBody(t *testing.T) {
192+
dir := t.TempDir()
193+
skillMD := `---
194+
name: body-skill
195+
version: 0.1.0
196+
---
197+
198+
# My Skill
199+
200+
Some **markdown** content with [links](https://example.com).
201+
202+
` + "```python\nprint('hello')\n```\n"
203+
require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(skillMD), 0644))
204+
205+
err := UpdateSkillMetaVersion(dir, "0.2.0")
206+
require.NoError(t, err)
207+
208+
data, err := os.ReadFile(filepath.Join(dir, "SKILL.md"))
209+
require.NoError(t, err)
210+
content := string(data)
211+
assert.Contains(t, content, "version: 0.2.0")
212+
assert.Contains(t, content, "Some **markdown** content with [links](https://example.com).")
213+
assert.Contains(t, content, "print('hello')")
214+
}
215+
108216
func TestValidateSlug(t *testing.T) {
109217
assert.NoError(t, ValidateSlug("my-skill"))
110218
assert.NoError(t, ValidateSlug("skill123"))

skills/commands/publish/skillmeta.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,54 @@ func stripQuotes(s string) string {
8787
return s
8888
}
8989

90+
// versionLineRegex matches a YAML version field (with optional quoting) inside front matter.
91+
var versionLineRegex = regexp.MustCompile(`(?m)^(version:\s*).*$`)
92+
93+
// UpdateSkillMetaVersion replaces the version value in the SKILL.md YAML front matter.
94+
// It only acts when a version: line already exists; it never inserts a new one.
95+
func UpdateSkillMetaVersion(skillDir, newVersion string) error {
96+
skillMDPath := filepath.Join(skillDir, "SKILL.md")
97+
// #nosec G304 -- path is constructed from user-provided skill directory argument
98+
data, err := os.ReadFile(skillMDPath)
99+
if err != nil {
100+
return fmt.Errorf("failed to read SKILL.md at %s: %w", skillMDPath, err)
101+
}
102+
103+
content := string(data)
104+
trimmed := strings.TrimSpace(content)
105+
if !strings.HasPrefix(trimmed, "---") {
106+
return fmt.Errorf("SKILL.md does not start with YAML frontmatter delimiter '---'")
107+
}
108+
109+
rest := trimmed[3:]
110+
endIdx := strings.Index(rest, "---")
111+
if endIdx < 0 {
112+
return fmt.Errorf("SKILL.md missing closing YAML frontmatter delimiter '---'")
113+
}
114+
115+
// Locate the front matter boundaries within the original (untrimmed) content
116+
fmStart := strings.Index(content, "---")
117+
fmEnd := strings.Index(content[fmStart+3:], "---")
118+
if fmEnd < 0 {
119+
return fmt.Errorf("SKILL.md missing closing YAML frontmatter delimiter '---'")
120+
}
121+
fmEnd += fmStart + 3
122+
123+
frontmatter := content[fmStart+3 : fmEnd]
124+
if !versionLineRegex.MatchString(frontmatter) {
125+
return nil
126+
}
127+
128+
updatedFM := versionLineRegex.ReplaceAllString(frontmatter, "${1}"+newVersion)
129+
updated := content[:fmStart+3] + updatedFM + content[fmEnd:]
130+
131+
// #nosec G306 G703 -- SKILL.md is a user-owned source file; path constructed from user-provided skill directory
132+
if err := os.WriteFile(skillMDPath, []byte(updated), 0644); err != nil {
133+
return fmt.Errorf("failed to write updated SKILL.md: %w", err)
134+
}
135+
return nil
136+
}
137+
90138
// ValidateSlug checks that a skill slug matches the required pattern.
91139
func ValidateSlug(slug string) error {
92140
if !slugRegex.MatchString(slug) {

0 commit comments

Comments
 (0)