Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions structure/frontmatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ const (
// maxShortSegmentPct is the percentage of comma segments that must be
// "short" (≤3 words) for the comma-list heuristic to fire.
maxShortSegmentPct = 60

// minAvgWordsPerSegment is the minimum average words per comma-separated
// segment. Sentences at or above this density are considered prose with
// inline lists rather than keyword dumps, even if many segments are short.
minAvgWordsPerSegment = 3
)

func checkDescriptionKeywordStuffing(ctx types.ResultContext, desc string) []types.Result {
Expand Down Expand Up @@ -165,12 +170,19 @@ func checkDescriptionKeywordStuffing(ctx types.ResultContext, desc string) []typ
}
if len(segments) >= minCommaSegments {
shortCount := 0
totalWords := 0
for _, seg := range segments {
words := strings.Fields(strings.TrimSpace(seg))
totalWords += len(words)
if len(words) <= 3 {
shortCount++
}
}
// Sentences with enough prose density are not keyword dumps,
// even if many individual segments are short.
if totalWords >= minAvgWordsPerSegment*len(segments) {
continue
}
if shortCount*100/len(segments) >= maxShortSegmentPct {
return []types.Result{ctx.Warnf(
"description has %d comma-separated segments, most very short — "+
Expand Down
8 changes: 8 additions & 0 deletions structure/frontmatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ func TestCheckFrontmatter_KeywordStuffing(t *testing.T) {
requireResultContaining(t, results, types.Warning, "comma-separated segments")
})

t.Run("prose with inline enumeration is fine (issue #71)", func(t *testing.T) {
desc := "Helps agents write and edit interface copy (microcopy) for digital products — buttons, labels, error messages, forms, onboarding flows, empty states, and help text. Use this skill whenever you need to produce or improve any text that appears in an app, website, or software UI. It applies four core quality standards (purposeful, concise, conversational, and clear) and ships with accessibility guidelines, research-backed readability benchmarks, error-message patterns, tone adaptation frameworks, and fillable templates."
s := makeSkill("/tmp/my-skill", "my-skill", desc)
results := CheckFrontmatter(s, Options{})
requireNoResultContaining(t, results, types.Warning, "keyword")
requireNoResultContaining(t, results, types.Warning, "comma-separated")
})

t.Run("description with abbreviations splits correctly", func(t *testing.T) {
desc := "Use for e.g. vector search and embedding workflows. Supports multiple backends, distributed indexing, and query optimization."
s := makeSkill("/tmp/my-skill", "my-skill", desc)
Expand Down
Loading