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
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ def _read_mode_table() -> dict[str, str]:
LOWERCASE_F_FIELD_CATEGORY,
}
)
HARD_CATEGORIES: frozenset[str] = frozenset(
{
TOOL_README_CATEGORY,
TOOL_CAPABILITY_CATEGORY,
CAPABILITY_SYNC_CATEGORY,
INJECTION_GUARD_CATEGORY,
}
)
ALL_CATEGORIES = HARD_CATEGORIES | SOFT_CATEGORIES

# ---------------------------------------------------------------------------
# Injection-guard constants (Pattern 4)
Expand Down Expand Up @@ -1648,8 +1657,19 @@ def main(argv: list[str] | None = None) -> int:
action="store_true",
help="Promote SOFT categories (advisory) to hard failures.",
)
parser.add_argument(
"--list-categories",
action="store_true",
help="Print every violation category name (SOFT ones marked) and exit.",
)
args = parser.parse_args(argv)

if args.list_categories:
for category in sorted(ALL_CATEGORIES):
suffix = " (advisory)" if category in SOFT_CATEGORIES else ""
print(f"{category}{suffix}")
return 0

skip = {c.strip() for c in args.skip_categories.split(",") if c.strip()}
violations = run_validation()
filtered = [v for v in violations if v.category not in skip]
Expand Down
13 changes: 13 additions & 0 deletions tools/skill-and-tool-validator/tests/test_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
_MODE_TAXONOMY,
_OFF_MODES,
_PRIVACY_EXTERNAL_CONTENT_MODES,
ALL_CATEGORIES,
ALLOWED_MODES,
FORBIDDEN_PATTERNS,
GH_LIST_CATEGORY,
HARD_CATEGORIES,
INJECTION_GUARD_CALLOUT_SENTINEL,
INJECTION_GUARD_CATEGORY,
INJECTION_GUARD_TODO_CATEGORY,
Expand Down Expand Up @@ -1391,6 +1393,10 @@ def test_lowercase_f_field_in_soft_categories(self) -> None:


class TestSoftCategories:
def test_all_categories_is_union_of_hard_and_soft(self) -> None:
assert ALL_CATEGORIES == HARD_CATEGORIES | SOFT_CATEGORIES
assert HARD_CATEGORIES.isdisjoint(SOFT_CATEGORIES)

def test_soft_categories_set(self) -> None:
assert PRINCIPLE_CATEGORY in SOFT_CATEGORIES
assert TRIGGER_PRESERVATION_CATEGORY in SOFT_CATEGORIES
Expand Down Expand Up @@ -1879,6 +1885,13 @@ def _make_valid_skill(root: Path, name: str) -> Path:


class TestMain:
def test_list_categories(self, capsys: pytest.CaptureFixture[str]) -> None:
rc = main(["--list-categories"])
assert rc == 0
out = capsys.readouterr().out
expected = [f"{c} (advisory)" if c in SOFT_CATEGORIES else c for c in sorted(ALL_CATEGORIES)]
assert out.strip().splitlines() == expected

def test_returns_0_when_no_violations(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
root = _skill_root(tmp_path)
_make_valid_skill(root, "my-skill")
Expand Down
Loading