diff --git a/skills/writing-skills/SKILL.md b/skills/writing-skills/SKILL.md index 86f0324..6c73f7a 100644 --- a/skills/writing-skills/SKILL.md +++ b/skills/writing-skills/SKILL.md @@ -116,6 +116,10 @@ allowed inside the opening tag, but a block tagged with multiple hosts (e.g. skips blocks tagged exclusively with `claude-code`. Nesting markers is not supported; behaviour with nested blocks is undefined. +The guard also skips fenced code blocks (`` ``` `` … `` ``` ``, including +opening fences with info strings like `` ```bash ``) so tool names can +appear freely in example code without triggering a false positive. + **Why angle-bracket form, not HTML-comment form?** The angle-bracket form is visible in PR diffs and skill-author reviews. The HTML-comment form (``) renders fully invisibly, which is a footgun: @@ -670,10 +674,10 @@ Deploying untested skills = deploying untested code. It's a violation of quality ## Skill Creation Checklist (TDD Adapted) -**IMPORTANT: Track each checklist item below as a task in your host's task system.** +Create a todo/task entry in your environment for each checklist item before starting. -Use TodoWrite to create a todo for EACH checklist item below. +Use TodoWrite to create one todo per item. **RED Phase - Write Failing Test:** diff --git a/tests/skill-content-grep.sh b/tests/skill-content-grep.sh index 68bb25f..095aec9 100755 --- a/tests/skill-content-grep.sh +++ b/tests/skill-content-grep.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash # tests/skill-content-grep.sh # Fails if forbidden host-specific tokens appear in skill text outside an -# allowed context ( block, model-tiers table, or an -# explicitly allowed file). +# allowed context ( block, fenced code block, +# model-tiers table, or an explicitly allowed file). # # Usage: # ./tests/skill-content-grep.sh # scan all files, exit 1 on violations # ./tests/skill-content-grep.sh 2>&1 | grep some-skill # scoped check -set -u +set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$REPO_ROOT" @@ -30,6 +30,11 @@ TOKENS=( # under skills/ or agents/ here. ALLOWED_FILES=( "agents/model-tiers.md" + # Claude-specific reference docs under writing-skills/: these discuss + # Anthropic model names and Claude-only tools as subject matter and are + # not portable skill bodies. + "skills/writing-skills/anthropic-best-practices.md" + "skills/writing-skills/persuasion-principles.md" ) tmp="$(mktemp)" @@ -53,13 +58,34 @@ find skills agents -type f -name '*.md' -print0 \ # → skip # → do NOT skip # → do NOT skip - # - Markers must occupy the whole line (only optional whitespace around - # the tag). A marker with trailing text is NOT recognised as a marker. + # - Skips fenced code blocks. A fence delimiter is any line whose + # non-whitespace content is 3+ backticks followed by an optional + # info-string (alphanumeric / _+-) and optional trailing whitespace. + # Opening and closing fences must use the same backtick width, so + # nested fences (4-backtick outer / 3-backtick inner example) are + # handled correctly without desynchronising the toggle. + # - Markers must occupy the whole line (only optional whitespace + # around the tag). A marker with trailing text is NOT recognised. # - Emits "LINENO:content" for every non-skipped line. annotated="$(awk ' - BEGIN { skip = 0; ln = 0 } + BEGIN { skip = 0; fence_width = 0; ln = 0 } { ln++ + # Detect fence delimiter: strip leading whitespace, count backticks, + # check remainder is a valid info-string + optional trailing space. + stripped = $0 + sub(/^[[:space:]]*/, "", stripped) + if (stripped ~ /^```/) { + n = 0 + s = stripped + while (length(s) > 0 && substr(s, 1, 1) == "`") { n++; s = substr(s, 2) } + if (s ~ /^[a-zA-Z0-9_+-]*[[:space:]]*$/) { + if (fence_width == 0) { fence_width = n; next } + if (n == fence_width) { fence_width = 0; next } + # Different width inside a fence — treat as content. + } + } + if (fence_width > 0) { next } if (/^[[:space:]]*[[:space:]]*$/) { skip = 1; next } if (/^[[:space:]]*<\/host>[[:space:]]*$/) { skip = 0; next } if (!skip) { print ln ":" $0 }