Files
boshu2__agentops/tests/python/test_scan_descriptions.py
Bo d972fa2090 Prepare AgentOps 4.0.0 plugins, skills and CLI release (#1143)
## What

Prepare AgentOps 4.0.0 across the Claude plugin, Codex plugin, skills
and CLI. Claude writers capture the supplied check status during its
original invocation, and plugin conformance verifies exact skill
membership and link destinations. Full release security now scans the
repository and blocks on Python collection failures that previously
produced a false green result.

## Why

The 3.6.0-to-current interval removes published commands and 20 skill
names, so this is a major release with migration instructions. Release
validation also exposed stale skill assertions and test prerequisites
that need to match the current product contracts without weakening
acceptance.

## How I tested

- Native Claude Opus/Haiku success, failing-check and direct-writer
trials: each check ran once, and the direct child returned plain JSON.
- Actual fresh installs and upgrades from 3.6.0 in isolated Codex and
Claude homes: 34 skills, expected agents, and exact installed package
bytes.
- Exact candidate `b721d02559e1495be6095ad97b820e88ceb4a049`: all 73
full repository gates, regeneration parity, and the complete local
release rehearsal passed. All 12 security tools ran with zero skips,
tool errors, critical findings or high-severity security findings. The
unchanged advisory policy reports 35 quality-high findings on unchanged
files.
- Python: 327 tests and 72 subtests passed. Hosted Bats: 1,509 passed,
31 environment-dependent skips, zero failures. Go
lint/build/vet/race/shuffle checks and CLI smoke/integration passed.
- All 11 hosted checks passed, including Windows correctness,
macOS/Linux installation, security, and the six-target no-publish
GoReleaser snapshot. Local archive checksums and a real macOS CLI
initialization/status/version smoke also passed.
- Fresh author-distinct review passed all four acceptance criteria and
all 35 changed paths with no unchecked acceptance. Canonical subject and
caller-intent verification passed; verdict digest
`68af2c935ed0106cd91b3950f5d168e662f4071f660fcbd113c36b7cd0f0426e` binds
manifest
`7affc77e25eaff69ba36c5ce05582b4f0385c954b76b62c02b97f97041f489b2`.

## Checklist

- [x] Breaking changes documented in the migration guide and complete
release notes.
- [x] No credentials or private runtime proof included.
- [x] Final full release checks pass on the exact candidate.
- [x] Fresh author-distinct final PASS is recorded before merge.

This prepares the release candidate; it does not publish a tag or
release.

Coverage limits remain explicit: native plugin tests used isolated macOS
homes and local marketplaces, guard installation remains opt-in, and
reader instructions do not prove sandbox confinement. Semgrep retains
pre-existing warning-level parser diagnostics. Snapshot metadata follows
the existing 3.6.0 tag; this is a packaging rehearsal, not a published
4.0.0 archive.
2026-09-13 17:21:16 -04:00

199 lines
7.2 KiB
Python

"""Unit + integration tests for skills/skill-builder/scripts/scan_descriptions.py.
Uses vanilla unittest + importlib so it runs without pytest installed.
Focus: the three trigger-detection forms (matching audit.sh), suggestion
generation, and the CLI entry point's exit codes.
"""
from __future__ import annotations
import importlib.util
import io
import sys
import tempfile
import unittest
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
from unittest.mock import patch
REPO_ROOT = Path(__file__).resolve().parents[2]
SCRIPT = REPO_ROOT / "skills" / "skill-builder" / "scripts" / "scan_descriptions.py"
def _load_module():
"""Load the scanner module by path (no package install required)."""
spec = importlib.util.spec_from_file_location("scan_descriptions", SCRIPT)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
# Register before exec so dataclass introspection can resolve the module
# (required on Python 3.14+ for importlib-loaded modules with dataclasses).
sys.modules[spec.name] = module
# Match direct script execution: its sibling modules are importable even
# when pytest's importlib mode does not modify sys.path for test files.
with patch.object(sys, "path", [str(SCRIPT.parent), *sys.path]):
spec.loader.exec_module(module)
return module
scan = _load_module()
def _write_skill(root: Path, name: str, frontmatter: str, body: str = "# Title\n") -> Path:
"""Create root/<name>/SKILL.md with the given frontmatter + body."""
skill_dir = root / name
skill_dir.mkdir(parents=True)
md = skill_dir / "SKILL.md"
md.write_text(f"---\n{frontmatter}\n---\n{body}", encoding="utf-8")
return md
class TestTriggerDetection(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
def tearDown(self):
self._tmp.cleanup()
def test_explicit_marker_in_description_detected(self):
md = _write_skill(
self.root,
"alpha",
"name: alpha\ndescription: 'Does a thing. Triggers: \"do thing\", \"alpha\".'",
)
result = scan.scan_skill(md)
self.assertTrue(result.has_trigger)
self.assertIn("inline-marker", result.forms)
def test_block_scalar_use_when_detected(self):
md = _write_skill(
self.root,
"beta",
"name: beta\ndescription: |\n Does a thing.\n **Use when:** you need a thing.",
)
result = scan.scan_skill(md)
self.assertTrue(result.has_trigger)
self.assertIn("block-marker", result.forms)
def test_triggers_list_with_three_items_detected(self):
md = _write_skill(
self.root,
"gamma",
"name: gamma\ndescription: Does a thing.\nmetadata:\n triggers:\n"
" - one\n - two\n - three",
)
result = scan.scan_skill(md)
self.assertTrue(result.has_trigger)
self.assertIn("metadata-list", result.forms)
def test_two_item_triggers_list_not_enough(self):
md = _write_skill(
self.root,
"delta",
"name: delta\ndescription: Does a thing.\nmetadata:\n triggers:\n - one\n - two",
)
result = scan.scan_skill(md)
self.assertFalse(result.has_trigger)
def test_plain_description_missing_trigger_gets_suggestion(self):
md = _write_skill(
self.root,
"scope-creep",
"name: scope-creep\ndescription: Audits the scope of a change.",
)
result = scan.scan_skill(md)
self.assertFalse(result.has_trigger)
self.assertEqual(result.forms, [])
self.assertTrue(result.suggestion.startswith("Triggers:"))
self.assertIn('"scope-creep"', result.suggestion)
def test_suggestion_has_no_duplicate_words(self):
md = _write_skill(
self.root, "compile", "name: compile\ndescription: Compile the corpus."
)
result = scan.scan_skill(md)
# "compile compile" must not appear — verb equals the only name token.
self.assertNotIn("compile compile", result.suggestion)
def test_invalid_frontmatter_is_rejected(self):
md = _write_skill(
self.root,
"invalid",
"name: invalid\ndescription: Unquoted colon: is invalid YAML.",
)
with self.assertRaisesRegex(scan.ProfileError, "frontmatter configuration error"):
scan.scan_skill(md)
class TestCli(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
def tearDown(self):
self._tmp.cleanup()
def test_strict_exits_one_when_a_skill_lacks_trigger(self):
_write_skill(self.root, "no-trig", "name: no-trig\ndescription: Plain description.")
with redirect_stdout(io.StringIO()):
code = scan.main([str(self.root), "--strict", "--quiet"])
self.assertEqual(code, 1)
def test_strict_exits_zero_when_all_have_triggers(self):
_write_skill(
self.root,
"ok",
"name: ok\ndescription: 'Does X. Triggers: \"ok\", \"do x\".'",
)
with redirect_stdout(io.StringIO()):
code = scan.main([str(self.root), "--strict", "--quiet"])
self.assertEqual(code, 0)
def test_missing_dir_exits_two(self):
code = scan.main([str(self.root / "does-not-exist")])
self.assertEqual(code, 2)
def test_unknown_profile_exits_two(self):
_write_skill(self.root, "plain", "name: plain\ndescription: Plain description.")
error = io.StringIO()
with patch.dict(scan.os.environ, {"SKILL_CONFORMANCE_PROFILE_ID": "unknown-test-profile"}):
with redirect_stderr(error):
code = scan.main([str(self.root), "--strict", "--quiet"])
self.assertEqual(code, 2)
self.assertIn("unknown profile", error.getvalue())
def test_probe_flow_form_allows_quoted_commas(self):
_write_skill(
self.root,
"deploy",
'name: deploy\ndescription: Run ci cd deploy.\ntrigger_probes: ["ci, cd deploy"]',
)
with redirect_stdout(io.StringIO()):
code = scan.main([str(self.root), "--probe", "ci, cd deploy"])
self.assertEqual(code, 0)
def test_json_output_reports_counts(self):
_write_skill(self.root, "a", "name: a\ndescription: Plain.")
_write_skill(self.root, "b", "name: b\ndescription: 'X. Triggers: \"b\", \"x\".'")
buf = io.StringIO()
with redirect_stdout(buf):
scan.main([str(self.root), "--json"])
import json
payload = json.loads(buf.getvalue())
self.assertEqual(payload["scanned"], 2)
self.assertEqual(payload["missing"], 1)
class TestRealCorpus(unittest.TestCase):
def test_scans_the_live_skills_directory(self):
skills_dir = REPO_ROOT / "skills"
results = scan.scan_corpus(skills_dir)
self.assertGreater(len(results), 0)
# Every result must carry the name of its directory or frontmatter.
self.assertTrue(all(r.name for r in results))
if __name__ == "__main__":
unittest.main()