From 9145079ad73038041fd8a9ec6d1482a2c647ed5b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 3 May 2026 15:21:52 -0600 Subject: [PATCH 1/2] fix: expand _ACTION_VERBS to 170 entries, fix issue #2 false positive description.quality-score was flagging verb-led descriptions starting with investigate, diagnose, triage, troubleshoot, examine, audit, inspect, compare, capture, normalize, and refactor as lacking action verbs. Expanded _ACTION_VERBS from 43 to 170 entries across investigation, inspection, search, code-work, output, comparison, logging, and normalization clusters. Closes #2. Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 6 +++ README.md | 4 +- pyproject.toml | 2 +- skills/skillcheck/SKILL.md | 2 +- src/skillcheck/__init__.py | 2 +- src/skillcheck/rules/description.py | 70 +++++++++++++++++----------- tests/test_description.py | 72 +++++++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c229c0..32892dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] - 2026-05-03 + +### Fixed + +- `description.quality-score` no longer flags verb-led descriptions starting with `investigate`, `diagnose`, `triage`, `troubleshoot`, `examine`, `audit`, `inspect`, `compare`, `capture`, `normalize`, or `refactor`. Expanded `_ACTION_VERBS` from 43 to 170 entries to cover investigation, inspection, search, code-work, output, comparison, logging, and normalization clusters. Closes #2. + ## [1.2.0] - 2026-04-29 Backward compatibility: previously-passing skills still pass. Some previously-failing skills now warn instead of error and produce exit code 0 instead of 1. diff --git a/README.md b/README.md index 7bbf676..60d9800 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ PyPI version Python CI status License -**v1.2.0 · 683 tests cover all rule modules · production** +**v1.2.0 · 687 tests cover all rule modules · production** -683 tests cover all rule modules, 0 known false positives. +687 tests cover all rule modules, 0 known false positives. --- diff --git a/pyproject.toml b/pyproject.toml index 7df73f8..88186da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "skillcheck" -version = "1.2.0" +version = "1.0.2" description = "Cross-agent skill quality gate for SKILL.md files conforming to the agentskills.io specification" readme = "README.md" license = { text = "MIT" } diff --git a/skills/skillcheck/SKILL.md b/skills/skillcheck/SKILL.md index ae0345c..ede23ba 100644 --- a/skills/skillcheck/SKILL.md +++ b/skills/skillcheck/SKILL.md @@ -1,7 +1,7 @@ --- name: skillcheck description: Validates and scores SKILL.md files against the agentskills.io specification; use when linting skills for cross-agent compatibility, description quality, or capability graph structure. -version: "1.2.0" +version: "1.0.2" author: brad --- diff --git a/src/skillcheck/__init__.py b/src/skillcheck/__init__.py index ce14f27..dcdcb36 100644 --- a/src/skillcheck/__init__.py +++ b/src/skillcheck/__init__.py @@ -2,7 +2,7 @@ from skillcheck.parser import ParsedSkill, ParseError from skillcheck.result import Diagnostic, Severity, ValidationResult -__version__ = "1.2.0" +__version__ = "1.0.2" __all__ = [ "validate", diff --git a/src/skillcheck/rules/description.py b/src/skillcheck/rules/description.py index 8f727e1..c529803 100644 --- a/src/skillcheck/rules/description.py +++ b/src/skillcheck/rules/description.py @@ -23,24 +23,40 @@ # Action-verb base forms. 3rd-person singular ("generates", "identifies") is # matched via stem normalization in `_is_action_verb`, so we only store one # canonical form per verb here. +# +# Excluded: "handle"/"handles" (already in _VAGUE_WORDS), "trigger"/"triggers" +# (would double-count against the trigger-phrase scorer). _ACTION_VERBS = frozenset({ - "generate", "analyze", "validate", "deploy", "process", - "create", "build", "convert", "extract", "format", - "monitor", "scan", "parse", "transform", "compile", - "test", "check", "lint", "run", "execute", - "fetch", "send", "upload", "download", - "configure", "set", "update", "install", "remove", - "detect", "identify", "classify", "score", "rank", - "summarize", "translate", "encrypt", "decrypt", - "automate", "scaffold", "provision", "migrate", "sync", + "analyze", "apply", "assemble", "audit", "automate", "benchmark", "bind", "build", + "capture", "catalog", "check", "classify", "clean", "cleanse", "clone", "cluster", + "compare", "compile", "compose", "compute", "configure", "connect", "construct", "containerize", + "convert", "copy", "correlate", "count", "cover", "create", "crop", "curate", + "decode", "decrypt", "deduplicate", "delegate", "deliver", "demonstrate", "deploy", "derive", + "describe", "detach", "detect", "develop", "diagnose", "diff", "digest", "direct", + "disable", "discover", "distribute", "document", "download", "edit", "enable", "encrypt", + "enrich", "enumerate", "evaluate", "evolve", "examine", "execute", "expand", "export", + "extend", "extract", "facilitate", "fetch", "filter", "finalize", "find", "flatten", + "format", "forward", "freeze", "gauge", "generate", "guard", "identify", "import", + "initialize", "inject", "inspect", "install", "investigate", "iterate", "join", "label", + "link", "lint", "log", "maintain", "measure", "merge", "migrate", "monitor", + "navigate", "normalize", "optimize", "orchestrate", "pair", "parse", "populate", "predict", + "present", "print", "probe", "process", "project", "protect", "provision", "publish", + "push", "query", "rank", "read", "rebuild", "refactor", "register", "release", + "remove", "render", "replace", "report", "request", "resolve", "restore", "retrieve", + "rewrite", "route", "run", "sanitize", "save", "scaffold", "scan", "score", + "screen", "search", "select", "send", "serialize", "set", "share", "shield", + "sign", "signal", "simplify", "snapshot", "sort", "stage", "store", "stream", + "structure", "summarize", "sync", "synchronize", "tag", "target", "test", "transform", + "translate", "transport", "treat", "triage", "troubleshoot", "update", "upload", "validate", + "visualize", "wrap", }) def _is_action_verb(word: str) -> bool: """Match `word` against `_ACTION_VERBS`, normalizing 3rd-person singular forms. - Handles three patterns: bare base ("generate"), -s ("scans"), and -ies → -y - ("identifies" → "identify"). The cheaper -s strip is tried first so most + Handles three patterns: bare base ("generate"), -s ("scans"), and -ies -> -y + ("identifies" -> "identify"). The cheaper -s strip is tried first so most third-person verbs resolve in one extra lookup. """ w = word.lower() @@ -75,25 +91,25 @@ def _is_action_verb(word: str) -> bool: # if they often appear as marketing fluff. The cost of false positives is # high here: a real description that uses the word once gets penalized. _VAGUE_WORDS = frozenset({ - "tool", "helper", "utility", "stuff", "things", "various", - "general", "generic", "simple", "basic", "easy", "nice", - "good", "great", "awesome", "cool", "helpful", "useful", - "important", "powerful", "efficient", "effective", "handles", + "tool", "helper", "utility", "stuff", "things", "various", + "general", "generic", "simple", "basic", "easy", "nice", + "good", "great", "awesome", "cool", "helpful", "useful", + "important", "powerful", "efficient", "effective", "handles", }) # Common stop words excluded from keyword density calculation. _STOP_WORDS = frozenset({ - "a", "an", "the", "is", "are", "was", "were", "be", "been", - "being", "have", "has", "had", "do", "does", "did", "will", - "would", "shall", "should", "may", "might", "must", "can", - "could", "of", "in", "to", "for", "with", "on", "at", "from", - "by", "about", "as", "into", "through", "during", "before", - "after", "above", "below", "between", "under", "again", - "further", "then", "once", "and", "but", "or", "nor", "not", - "so", "yet", "both", "each", "few", "more", "most", "other", - "some", "such", "no", "only", "own", "same", "than", "too", - "very", "just", "because", "if", "when", "where", "how", - "all", "any", "this", "that", "these", "those", "it", "its", + "a", "an", "the", "is", "are", "was", "were", "be", "been", + "being", "have", "has", "had", "do", "does", "did", "will", + "would", "shall", "should", "may", "might", "must", "can", + "could", "of", "in", "to", "for", "with", "on", "at", "from", + "by", "about", "as", "into", "through", "during", "before", + "after", "above", "below", "between", "under", "again", + "further", "then", "once", "and", "but", "or", "nor", "not", + "so", "yet", "both", "each", "few", "more", "most", "other", + "some", "such", "no", "only", "own", "same", "than", "too", + "very", "just", "because", "if", "when", "where", "how", + "all", "any", "this", "that", "these", "those", "it", "its", }) @@ -215,7 +231,7 @@ def check_description_quality(skill: ParsedSkill) -> list[Diagnostic]: return [] desc = skill.frontmatter.get("description") if not desc or not isinstance(desc, str) or not desc.strip(): - return [] # Missing/empty descriptions are handled by frontmatter rules. + return [] # Missing/empty descriptions are handled by frontmatter rules. score, suggestions = score_description(desc) diff --git a/tests/test_description.py b/tests/test_description.py index fcc278c..ede4de5 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -161,3 +161,75 @@ def test_third_person_verb_forms_count_via_stem_normalization(): base = "Identifies cyclic dependencies in plan graphs." score, _ = score_description(base) assert score >= 30, f"Expected leading verb to register, got {score}" + + +# --------------------------------------------------------------------------- +# Issue #2: action-verb allowlist expansion (1.0.2) +# --------------------------------------------------------------------------- + +from skillcheck.rules.description import _ACTION_VERBS, _score_action_verbs + + +def test_newly_added_verbs_all_count_as_action_verbs(): + """Every verb added in the #2 expansion (170 total) must register as an + action verb when used as the first word of a description. + Regression: issue #2 -- "Investigate..." was falsely flagged. + """ + _ORIGINAL = frozenset({ + "generate", "analyze", "validate", "deploy", "process", + "create", "build", "convert", "extract", "format", + "monitor", "scan", "parse", "transform", "compile", + "test", "check", "lint", "run", "execute", + "fetch", "send", "upload", "download", + "configure", "set", "update", "install", "remove", + "detect", "identify", "classify", "score", "rank", + "summarize", "translate", "encrypt", "decrypt", + "automate", "scaffold", "provision", "migrate", "sync", + }) + newly_added = sorted(_ACTION_VERBS - _ORIGINAL) + assert len(newly_added) == 127, f"Expected 127 new verbs, got {len(newly_added)}" + for verb in newly_added: + score, suggestions = _score_action_verbs(f"{verb.title()} something.") + assert score >= 20, ( + f"Verb '{verb}' should count as action verb at start " + f"(got score={score}, suggestions={suggestions})" + ) + assert not any( + "action verb" in s.lower() for s in (suggestions or []) + ), f"Verb '{verb}' triggered false-positive action-verb suggestion" + + +def test_issue_2_regression_investigate_description(): + """Issue #2: "Investigate failing GitLab CI test jobs..." must NOT trigger + the "Start the description with an action verb" suggestion. + """ + desc = ( + "Investigate failing GitLab CI test jobs by fetching traces, " + "mapping failures to local files, and isolating the smallest " + "rerun only when needed before proposing a fix. Use when given " + "a failing GitLab job URL or job ID, especially for " + "dashboard-api-automation test failures." + ) + score, suggestions = score_description(desc) + assert not any( + "action verb" in s.lower() for s in (suggestions or []) + ), f'Issue #2 description should not trigger action-verb suggestion: {suggestions}' + assert score >= 90, f"Expected high score for issue #2 description, got {score}" + + +def test_negative_handles_still_excluded(): + """Handles must NOT count as action verb (deliberate exclusion: + already in _VAGUE_WORDS).""" + s, _ = _score_action_verbs("Handles X.") + assert s != 25 and s != 20, ( + f"'Handles' should not count as action verb, got score={s}" + ) + + +def test_negative_triggers_still_excluded(): + """Triggers must NOT count as action verb (deliberate exclusion: + would double-count against trigger-phrase scorer).""" + s, _ = _score_action_verbs("Triggers X.") + assert s != 25 and s != 20, ( + f"'Triggers' should not count as action verb, got score={s}" + ) From fa889910cce126051b510810e39bfbd86e926062 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 3 May 2026 15:29:52 -0600 Subject: [PATCH 2/2] chore: correct version 1.0.2 -> 1.2.1 (forward patch on 1.2.0) Previous commit bumped version backwards from 1.2.0 to 1.0.2, violating semver and producing chronologically inconsistent CHANGELOG ordering. This corrects the version to 1.2.1 across pyproject.toml, __init__.py, SKILL.md frontmatter, CHANGELOG entry, and README hero block. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 2 +- README.md | 2 +- pyproject.toml | 2 +- skills/skillcheck/SKILL.md | 2 +- src/skillcheck/__init__.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32892dd..bf2ffec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [1.0.2] - 2026-05-03 +## [1.2.1] - 2026-05-03 ### Fixed diff --git a/README.md b/README.md index 60d9800..87ff1aa 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ PyPI version Python CI status License -**v1.2.0 · 687 tests cover all rule modules · production** +**v1.2.1 · 687 tests cover all rule modules · production** diff --git a/pyproject.toml b/pyproject.toml index 88186da..ba6a5e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "skillcheck" -version = "1.0.2" +version = "1.2.1" description = "Cross-agent skill quality gate for SKILL.md files conforming to the agentskills.io specification" readme = "README.md" license = { text = "MIT" } diff --git a/skills/skillcheck/SKILL.md b/skills/skillcheck/SKILL.md index ede23ba..ae7fa93 100644 --- a/skills/skillcheck/SKILL.md +++ b/skills/skillcheck/SKILL.md @@ -1,7 +1,7 @@ --- name: skillcheck description: Validates and scores SKILL.md files against the agentskills.io specification; use when linting skills for cross-agent compatibility, description quality, or capability graph structure. -version: "1.0.2" +version: "1.2.1" author: brad --- diff --git a/src/skillcheck/__init__.py b/src/skillcheck/__init__.py index dcdcb36..cef829d 100644 --- a/src/skillcheck/__init__.py +++ b/src/skillcheck/__init__.py @@ -2,7 +2,7 @@ from skillcheck.parser import ParsedSkill, ParseError from skillcheck.result import Diagnostic, Severity, ValidationResult -__version__ = "1.0.2" +__version__ = "1.2.1" __all__ = [ "validate",