[BUG] write-a-skill checklist runner: check 1 fails on `>-` descriptions and on "use whenever" phrasing
write-a-skill's checklist runner has two defects in check 1 ("Description includes triggers") that make it report correct descriptions as failures. Both are still on main at engineering/write-a-skill/skills/write-a-skill/scripts/skill_review_checklist_runner.py.
I hit these while bringing a private plugin up to the 6-item standard, and measured them across 774 SKILL.md files: 33 skills fail check 1 while carrying a perfectly good trigger phrase. A gate that fails correct work is worse than one that does not run, because it teaches people to ignore it.
Defect A: block scalar indicators are compared by equality
extract_frontmatter_description() line 59:
if val == ">" or val == "|":YAML block scalar indicators may carry a chomping indicator (>-, >+, |-, |+) and/or an explicit indentation digit (>2). All are valid. Only the two bare forms are handled, so for any other variant the function falls through and returns the indicator itself as the description. Check 1 then compares trigger patterns against the literal string ">-" and can never pass, whatever the description says.
description: >-
Does a thing. Use when asked.Check 1 fails. Change >- to > and it passes, with identical text.
This is easy to miss because Claude Code parses frontmatter with a real YAML parser, so the skill itself works fine. Only the checklist is wrong.
Defect B: the trigger vocabulary is narrower than how people write
TRIGGER_PATTERNS is a fixed list of two-word strings. It accepts use when but rejects every one of these, all taken verbatim from shipped skills:
| written | why it fails |
|---|---|
Use whenever building a flow |
\buse\s+when\b needs a word boundary after when; whenever continues |
Use this whenever Matt says... |
filler word between verb and cue |
Use it whenever an audit is wanted |
same |
Use this skill whenever the user says... |
same |
This skill should be used when... |
used, not use |
Use on EVERY new store build |
cue is on every |
Invoke at the start of any session |
cue is at the start of |
Load this BEFORE writing or editing... |
verb is load |
Each of these is a clearer trigger than many that pass. The check is measuring phrasing, not presence.
Proposed fix
Match an invocation verb followed by a temporal or conditional cue, rather than enumerating pairs. Both halves must still be present, so prose like "runs on macOS" or "teaches you to use design tokens" keeps failing.
- if val == ">" or val == "|":
+ # A block scalar indicator may carry a chomping ("-", "+") and/or an explicit
+ # indentation digit: ">", ">-", ">+", "|2", "|-2" are all valid YAML.
+ if re.fullmatch(r"[>|][0-9]*[-+]?|[>|][-+]?[0-9]*", val):-TRIGGER_PATTERNS = [
- re.compile(r"\buse\s+when\b", re.IGNORECASE),
- re.compile(r"\buse\s+for\b", re.IGNORECASE),
- re.compile(r"\buse\s+before\b", re.IGNORECASE),
- re.compile(r"\buse\s+during\b", re.IGNORECASE),
- re.compile(r"\buse\s+after\b", re.IGNORECASE),
- re.compile(r"\buse\s+while\b", re.IGNORECASE),
- re.compile(r"\binvoke\s+when\b", re.IGNORECASE),
- re.compile(r"\binvoke\s+before\b", re.IGNORECASE),
- re.compile(r"\binvoke\s+after\b", re.IGNORECASE),
- re.compile(r"\btrigger\s+when\b", re.IGNORECASE),
- re.compile(r"\bapply\s+when\b", re.IGNORECASE),
- re.compile(r"\brun\s+when\b", re.IGNORECASE),
- re.compile(r"\brun\s+before\b", re.IGNORECASE),
-]
+_TRIGGER_VERB = r"(?:use|used|using|invoke|invoked|apply|applied|run|load|fire|trigger|reach\s+for)"
+_TRIGGER_FILLER = r"(?:\s+(?:this|it|its|the|a|an|skill))*"
+_TRIGGER_CUE = (
+ r"(?:when(?:ever)?|before|after|during|while"
+ r"|at\s+the\s+(?:start|point|moment)\s+of"
+ r"|on\s+(?:every|any|each))"
+)
+
+TRIGGER_PATTERNS = [
+ re.compile(rf"\b{_TRIGGER_VERB}\b{_TRIGGER_FILLER}\s+{_TRIGGER_CUE}\b", re.IGNORECASE),
+ # "Use for X" has no temporal cue but is long-standing and unambiguous.
+ re.compile(r"\buse\s+for\b", re.IGNORECASE),
+]Measured effect
Re-ran both versions over 774 SKILL.md files (this marketplace's plugins plus a private one):
now passing that used to fail : 33
still failing (genuinely no trigger) : 140
regressions (passed before, fails now): 0140 still fail, so the check keeps its teeth; it is not simply more permissive.
Test
Twenty cases covering both defects, including four negatives that must keep failing:
# every block scalar indicator must be read, not returned verbatim
('folded >-', block('>-', 'Does a thing. Use when asked.'), True),
('literal |-', block('|-', 'Does a thing. Use when asked.'), True),
# real phrasings from shipped skills
('use whenever', plain('Does a thing. Use whenever building a flow.'), True),
('should be used when', plain('This skill should be used when the user says go.'), True),
('invoke at the start of', plain('A router. Invoke at the start of any session.'), True),
('load this before', plain('A reference. Load this before editing any schema.'), True),
# negatives: these must NOT start passing
('no trigger at all', plain('Create developer handoff specifications with measurements.'), False),
('runs on macOS', plain('A linter. Runs on macOS and Linux, written in Python.'), False),
('mentions use, no cue', plain('Teaches you to use design tokens rather than raw values.'), False),Happy to open a PR with the patch and the full test file if that is useful.
Source: alirezarezvani/claude-skills