mirror of
https://github.com/addyosmani/agent-skills.git
synced 2026-08-12 18:07:26 +02:00
fix(validator): reject fenced-block headings and negated triggers (#387)
Two heuristic bugs in the section and trigger checks, surfaced in
addyosmani#387 and triaged by @addyosmani and @nucliweb:
Section check: content.includes('## Overview') matches headings inside
fenced code blocks and sub-headings (### Overview) because substring
matching can't distinguish prose from examples. Replace with a
line-anchored regex match against content that has had fenced code
blocks stripped, so only real top-level headings satisfy the check.
Trigger check: 'Do not use when testing' satisfied the DESCRIPTION_TRIGGER
regex because it contains 'use … when'. Add a negation pattern and reject
descriptions where the only trigger match is negated.
Verified: all 24 skills pass (output unchanged — no current skill has
either bug pattern). Crafted edge cases confirm the fixes catch both
issues. Eval suite green, rank-1 rate unchanged at 86%.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,7 +34,10 @@ const KEBAB_CASE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
// A description must state WHEN to use the skill, not just what it does
|
||||
// (docs/skill-anatomy.md → Required). Accept the canonical "Use when …"
|
||||
// plus the equivalent "Use before/after/during …" phrasings in use today.
|
||||
const DESCRIPTION_TRIGGER = /\buse (this )?when\b|\buse (before|after|during)\b/i;
|
||||
// Reject negated forms ("Do not use when …", "Don't use when …") — those
|
||||
// describe exclusions, not trigger conditions.
|
||||
const DESCRIPTION_TRIGGER = /\buse (this )?when\b|\buse (before|after|during)\b/i;
|
||||
const DESCRIPTION_TRIGGER_NEGATE = /\b(do not|don't|never) use (this )?(when|before|after|during)\b/i;
|
||||
|
||||
// Sections every standard SKILL.md must contain.
|
||||
// Each entry is an array of acceptable heading strings — the first
|
||||
@@ -74,6 +77,14 @@ const SKILL_REF_PATTERNS = [
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Strip fenced code blocks from markdown content so that headings, references,
|
||||
* and trigger phrases inside examples or templates are not matched by lint rules.
|
||||
*/
|
||||
function stripFencedCodeBlocks(content) {
|
||||
return content.replace(/^(`{3,})[^\n]*\n[\s\S]*?^\1\s*$/gm, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse YAML-style frontmatter from the top of a markdown file.
|
||||
* Returns a key→value object, or null if no frontmatter block found.
|
||||
@@ -150,7 +161,10 @@ function lintSkillContent(dirName, content, knownSkills) {
|
||||
` (agents inject this into the system prompt)`
|
||||
);
|
||||
}
|
||||
if (!DESCRIPTION_TRIGGER.test(fm.description)) {
|
||||
const hasTrigger = DESCRIPTION_TRIGGER.test(fm.description);
|
||||
const onlyNegated = hasTrigger && DESCRIPTION_TRIGGER_NEGATE.test(fm.description)
|
||||
&& !fm.description.replace(DESCRIPTION_TRIGGER_NEGATE, '').match(DESCRIPTION_TRIGGER);
|
||||
if (!hasTrigger || onlyNegated) {
|
||||
errors.push(
|
||||
`Description has no 'when to use' trigger — add a "Use when …" clause ` +
|
||||
`(skill-anatomy.md: Required — the description must say both what the skill does and when to use it)`
|
||||
@@ -176,8 +190,15 @@ function lintSkillContent(dirName, content, knownSkills) {
|
||||
exempt = dirName in SECTION_EXEMPT_SKILLS;
|
||||
|
||||
if (!exempt) {
|
||||
// Strip fenced code blocks so headings inside examples/templates don't
|
||||
// satisfy the check, and match headings at the start of a line so
|
||||
// `### Verification` inside a block doesn't satisfy `## Verification`.
|
||||
const proseContent = stripFencedCodeBlocks(content);
|
||||
for (const aliases of REQUIRED_SECTIONS) {
|
||||
const found = aliases.some(heading => content.includes(heading));
|
||||
const found = aliases.some(heading => {
|
||||
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
return new RegExp(`^${escaped}\\s*$`, 'm').test(proseContent);
|
||||
});
|
||||
if (!found) {
|
||||
errors.push(`Missing required section: ${aliases[0]}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user