| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- #!/usr/bin/env python3
- """Guard trigger detection in run_triggers.py.
- The stream-json init event lists every discovered skill by name, so any
- detection that substring-matches the whole transcript reports a 100% trigger
- rate. These tests pin the rule: only tool_use events (a Skill call naming the
- synthetic skill, or a Read inside its directory) count as a load, and
- substring-style load signals are rejected outright.
- Run with: python3 -m pytest test_trigger_detection.py
- (or plain `python3 test_trigger_detection.py` for a lightweight self-check).
- """
- import json
- import sys
- from pathlib import Path
- SCRIPTS_DIR = Path(__file__).resolve().parents[1]
- sys.path.insert(0, str(SCRIPTS_DIR))
- from run_triggers import detect_load, validate_load_signal # noqa: E402
- NAME = "my-skill-trig-abc12345"
- def line(obj) -> str:
- return json.dumps(obj)
- def init_event() -> str:
- # Claude Code's init event advertises every discovered skill.
- return line({"type": "system", "subtype": "init",
- "tools": ["Skill", "Read", "Bash"],
- "slash_commands": [], "skills": [NAME, "other-skill"]})
- def assistant(content) -> str:
- return line({"type": "assistant", "message": {"content": content}})
- def test_init_event_alone_is_not_a_load():
- transcript = "\n".join([
- init_event(),
- assistant([{"type": "text", "text": "I can't help with that."}]),
- line({"type": "result", "usage": {}}),
- ])
- assert detect_load(transcript, {}, NAME) is False
- def test_text_mention_is_not_a_load():
- transcript = assistant(
- [{"type": "text", "text": f"There is a skill called {NAME} available."}])
- assert detect_load(transcript, {}, NAME) is False
- def test_skill_tool_call_is_a_load():
- transcript = "\n".join([
- init_event(),
- assistant([{"type": "tool_use", "name": "Skill",
- "input": {"skill": NAME}}]),
- ])
- assert detect_load(transcript, {}, NAME) is True
- def test_read_of_synthetic_skill_md_is_a_load():
- transcript = "\n".join([
- init_event(),
- assistant([{"type": "tool_use", "name": "Read",
- "input": {"file_path":
- f"/tmp/stage/.claude/skills/{NAME}/SKILL.md"}}]),
- ])
- assert detect_load(transcript, {}, NAME) is True
- def test_unrelated_tool_calls_are_not_a_load():
- transcript = "\n".join([
- init_event(),
- assistant([{"type": "tool_use", "name": "Read",
- "input": {"file_path": "/tmp/stage/notes.md"}}]),
- assistant([{"type": "tool_use", "name": "Skill",
- "input": {"skill": "other-skill"}}]),
- assistant([{"type": "tool_use", "name": "Bash",
- "input": {"command": f"echo {NAME}"}}]),
- ])
- assert detect_load(transcript, {}, NAME) is False
- def test_custom_tool_names_from_load_signal():
- sig = {"skill_tool": "InvokeSkill", "read_tool": "OpenFile"}
- hit = assistant([{"type": "tool_use", "name": "InvokeSkill",
- "input": {"name": NAME}}])
- miss = assistant([{"type": "tool_use", "name": "Skill",
- "input": {"skill": NAME}}])
- assert detect_load(hit, sig, NAME) is True
- assert detect_load(miss, sig, NAME) is False, \
- "default tool name must not fire when the adapter renames it"
- def test_garbage_lines_do_not_crash():
- transcript = "not json\n\n{\"type\": 42}\n[1,2,3]\n"
- assert detect_load(transcript, {}, NAME) is False
- def test_string_load_signal_rejected():
- for fn, args in ((validate_load_signal, ({"type": "string"},)),
- (detect_load, ("", {"type": "string"}, NAME))):
- try:
- fn(*args)
- except ValueError:
- pass
- else:
- raise AssertionError(
- f"{fn.__name__} accepted a substring load_signal")
- if __name__ == "__main__":
- test_init_event_alone_is_not_a_load()
- test_text_mention_is_not_a_load()
- test_skill_tool_call_is_a_load()
- test_read_of_synthetic_skill_md_is_a_load()
- test_unrelated_tool_calls_are_not_a_load()
- test_custom_tool_names_from_load_signal()
- test_garbage_lines_do_not_crash()
- test_string_load_signal_rejected()
- print("ok: trigger detection counts tool calls only; substring rejected")
|