---
phase: 02-scanners
plan: 02
type: execute
wave: 2
depends_on: ["02-01"]
files_modified:
  - src/meshulash_guard/scanners/topic.py
  - src/meshulash_guard/scanners/toxicity.py
  - src/meshulash_guard/scanners/cyber.py
  - src/meshulash_guard/scanners/jailbreak.py
  - src/meshulash_guard/scanners/__init__.py
  - src/meshulash_guard/__init__.py
autonomous: true

must_haves:
  truths:
    - "TopicScanner(labels=[TopicLabel.HEALTH]) produces a guardline spec with tc=[['topic', 'Health']] and types.tc=True"
    - "ToxicityScanner(labels=[ToxicityLabel.HATE_SPEECH]) produces a guardline spec with tc=[['hate_speech', 'hate speech']]"
    - "CyberScanner(labels=[CyberLabel.MALWARE_ATTACKS]) produces a guardline spec with tc=[['cysecbench', 'Malware Attacks']]"
    - "JailbreakScanner(labels=[JailbreakLabel.JAILBREAK]) produces a guardline spec with tc=[['jailbreak', 'jailbreak']]"
    - "All five scanner classes and their label enums are importable from meshulash_guard.scanners"
    - "from meshulash_guard import Guard, PIIScanner, TopicScanner works (top-level re-exports)"
  artifacts:
    - path: "src/meshulash_guard/scanners/topic.py"
      provides: "TopicScanner class and TopicLabel enum"
      contains: "class TopicScanner"
    - path: "src/meshulash_guard/scanners/toxicity.py"
      provides: "ToxicityScanner class and ToxicityLabel enum"
      contains: "class ToxicityScanner"
    - path: "src/meshulash_guard/scanners/cyber.py"
      provides: "CyberScanner class and CyberLabel enum"
      contains: "class CyberScanner"
    - path: "src/meshulash_guard/scanners/jailbreak.py"
      provides: "JailbreakScanner class and JailbreakLabel enum"
      contains: "class JailbreakScanner"
    - path: "src/meshulash_guard/scanners/__init__.py"
      provides: "Re-exports all scanners and label enums"
      contains: "PIIScanner"
    - path: "src/meshulash_guard/__init__.py"
      provides: "Top-level scanner re-exports"
      contains: "PIIScanner"
  key_links:
    - from: "src/meshulash_guard/scanners/topic.py"
      to: "server TC head 'topic'"
      via: "_TC_HEAD class constant"
      pattern: "_TC_HEAD.*=.*topic"
    - from: "src/meshulash_guard/scanners/toxicity.py"
      to: "server TC head 'hate_speech'"
      via: "_TC_HEAD class constant"
      pattern: "_TC_HEAD.*=.*hate_speech"
    - from: "src/meshulash_guard/scanners/cyber.py"
      to: "server TC head 'cysecbench'"
      via: "_TC_HEAD class constant"
      pattern: "_TC_HEAD.*=.*cysecbench"
    - from: "src/meshulash_guard/scanners/jailbreak.py"
      to: "server TC head 'jailbreak'"
      via: "_TC_HEAD class constant"
      pattern: "_TC_HEAD.*=.*jailbreak"
    - from: "src/meshulash_guard/scanners/__init__.py"
      to: "all scanner modules"
      via: "from .pii import PIIScanner, PIILabel etc."
      pattern: "from \\.pii import"
---

<objective>
Implement the four TC-based scanners (TopicScanner, ToxicityScanner, CyberScanner, JailbreakScanner) with their scoped label enums, then wire all five scanners into the package's public API via __init__.py re-exports.

Purpose: Developers need all five scanner types importable and functional. The TC scanners all follow the same pattern: scoped label enum + _TC_HEAD class constant + to_guardline_spec() that builds tc-type specs. The package wiring makes scanners accessible via `from meshulash_guard.scanners import TopicScanner` and `from meshulash_guard import PIIScanner`.

Output: Four TC scanner files + updated __init__.py files with full scanner re-exports.
</objective>

<execution_context>
@/Users/shalev/.claude/get-shit-done/workflows/execute-plan.md
@/Users/shalev/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-scanners/02-CONTEXT.md
@.planning/phases/02-scanners/02-RESEARCH.md
@.planning/phases/02-scanners/02-01-SUMMARY.md
@src/meshulash_guard/enums.py
@src/meshulash_guard/scanners/base.py
@src/meshulash_guard/scanners/pii.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create TopicScanner, ToxicityScanner, CyberScanner, and JailbreakScanner</name>
  <files>
    src/meshulash_guard/scanners/topic.py
    src/meshulash_guard/scanners/toxicity.py
    src/meshulash_guard/scanners/cyber.py
    src/meshulash_guard/scanners/jailbreak.py
  </files>
  <action>
Create four TC scanner files, all following the same pattern. Each file contains a scoped label enum and a scanner class.

**CRITICAL: TC label enum values must EXACTLY match the strings in the server's `label_config.json`.** The server's TC heads use these exact strings (case-sensitive, with spaces). The SDK enum member NAMES are uppercase/underscored for Python ergonomics, but the VALUES must be the exact server strings.

**Common TC scanner pattern (use for all 4):**

```python
from __future__ import annotations
from typing import Sequence
from ..enums import Action, Condition, _StrValueMixin
from .base import BaseScanner


class {Label}(_StrValueMixin):
    """Labels for {scanner_name} detection."""
    # ... labels with exact server string values ...
    ALL = "__ALL__"


class {Scanner}(BaseScanner):
    _TC_HEAD = "{head_name}"  # server TC head name (hidden from developer)

    def __init__(
        self,
        labels: Sequence[{Label}],
        action: Action = Action.BLOCK,
        condition: Condition = Condition.ANY,
        threshold: float | None = None,
        allowlist: list[str] | None = None,
    ) -> None:
        if not labels:
            raise ValueError(
                "{Scanner} requires at least one label. "
                "Use {Label}.ALL to scan everything."
            )
        self._labels = list(labels)
        self._action = action
        self._condition = condition
        self._threshold = threshold
        self._allowlist = allowlist or []

    def to_guardline_spec(self) -> dict:
        if any(lbl.value == "__ALL__" for lbl in self._labels):
            all_labels = [m for m in type(self._labels[0]) if m.value != "__ALL__"]
        else:
            all_labels = self._labels
        tc_pairs = [[self._TC_HEAD, lbl.value] for lbl in all_labels]
        spec: dict = {
            "name": self.__class__.__name__,
            "condition": str(self._condition),
            "action": str(self._action),
            "level": 2,
            "types": {"regex": False, "ner": False, "tc": True},
            "required": {"regex": [], "ner": [], "tc": tc_pairs},
            "allowlist": list(self._allowlist),
            "bundles": [],
            "k": 0,
        }
        if self._threshold is not None:
            spec["threshold"] = self._threshold
        return spec
```

**1. TopicScanner (`scanners/topic.py`):**
- `_TC_HEAD = "topic"`
- Default action: `Action.BLOCK`
- TopicLabel enum values (EXACT server strings from label_config.json):
  ```
  ALBUM = "Album"
  ANIMAL = "Animal"
  ARTIST = "Artist"
  ATHLETE = "Athlete"
  BUILDING = "Building"
  BUSINESS_FINANCE = "Business & Finance"
  COMPANY = "Company"
  COMPUTERS_INTERNET = "Computers & Internet"
  EDUCATION_REFERENCE = "Education & Reference"
  EDUCATIONAL_INSTITUTION = "EducationalInstitution"
  ENTERTAINMENT_MUSIC = "Entertainment & Music"
  ETHNICITY_RACE = "Ethnicity & Race"
  FAMILY_RELATIONSHIPS = "Family & Relationships"
  FILM = "Film"
  HEALTH = "Health"
  IDENTITY_ATTACK = "Identity_Attack"
  LEGAL_REGULATIONS = "Legal & Regulations"
  MEAN_OF_TRANSPORTATION = "MeanOfTransportation"
  NATURAL_PLACE = "NaturalPlace"
  OFFICE_HOLDER = "OfficeHolder"
  PHILOSOPHICAL_CONCEPT = "Philosophical Concept"
  PLANT = "Plant"
  POLITICS_GOVERNMENT = "Politics & Government"
  SCIENCE_MATHEMATICS = "Science & Mathematics"
  SEXUAL_EXPLICIT = "Sexual_Explicit"
  SOCIETY_CULTURE = "Society & Culture"
  SOFTWARE_DEVELOPMENT_CODE = "Software Development & Code"
  SPORTS = "Sports"
  VILLAGE = "Village"
  WRITTEN_WORK = "WrittenWork"
  ALL = "__ALL__"
  ```

**2. ToxicityScanner (`scanners/toxicity.py`):**
- `_TC_HEAD = "hate_speech"`
- Default action: `Action.BLOCK`
- ToxicityLabel enum values (EXACT server strings):
  ```
  TOXICITY = "Toxicity"
  HATE_SPEECH = "hate speech"
  NON_HATE = "non-hate"
  OFFENSIVE_LANGUAGE = "offensive language"
  ALL = "__ALL__"
  ```
  Note: "non-hate" is included for completeness (developers may want to explicitly check for non-hate as part of a contextual condition). The server uses these exact lowercase/mixed-case strings.

**3. CyberScanner (`scanners/cyber.py`):**
- `_TC_HEAD = "cysecbench"`
- Default action: `Action.BLOCK`
- CyberLabel enum values (EXACT server strings):
  ```
  CLOUD_ATTACKS = "Cloud Attacks"
  CONTROL_SYSTEM_ATTACKS = "Control System Attacks"
  CRYPTOGRAPHIC_ATTACKS = "Cryptographic Attacks"
  EVASION_TECHNIQUES = "Evasion Techniques"
  HARDWARE_ATTACKS = "Hardware Attacks"
  INTRUSION_TECHNIQUES = "Intrusion Techniques"
  IOT_ATTACKS = "IoT Attacks"
  MALWARE_ATTACKS = "Malware Attacks"
  NETWORK_ATTACKS = "Network Attacks"
  NONE = "None"
  WEB_APPLICATION_ATTACKS = "Web Application Attacks"
  ALL = "__ALL__"
  ```

**4. JailbreakScanner (`scanners/jailbreak.py`):**
- `_TC_HEAD = "jailbreak"`
- Default action: `Action.BLOCK`
- JailbreakLabel enum values (EXACT server strings):
  ```
  BENIGN = "benign"
  JAILBREAK = "jailbreak"
  ALL = "__ALL__"
  ```
  Add a class-level docstring note: "NOTE: The jailbreak TC head is coming soon on the server. Results may be empty until the server deploys the jailbreak classification head."

Implementation notes:
- ALL scanners use `_StrValueMixin` from `..enums` for Python 3.12+ compatibility.
- ALL scanners validate labels non-empty in __init__.
- The `condition` parameter defaults to `Condition.ANY`.
- The `threshold` parameter is optional; include in spec only if set.
- The `allowlist` parameter filters are passed through to the server.
- The `k` field is always `0` for TC scanners (not configurable in v1).
- Do NOT set `types.regex` or `types.ner` to True for TC scanners.
  </action>
  <verify>
```bash
cd /Users/shalev/Desktop/meshulash_guard && .venv/bin/python -c "
from meshulash_guard.scanners.topic import TopicScanner, TopicLabel
from meshulash_guard.scanners.toxicity import ToxicityScanner, ToxicityLabel
from meshulash_guard.scanners.cyber import CyberScanner, CyberLabel
from meshulash_guard.scanners.jailbreak import JailbreakScanner, JailbreakLabel
from meshulash_guard.enums import Action, Condition

# TopicScanner
topic = TopicScanner(labels=[TopicLabel.HEALTH, TopicLabel.POLITICS_GOVERNMENT])
spec = topic.to_guardline_spec()
assert spec['types']['tc'] == True
assert spec['types']['regex'] == False
assert spec['types']['ner'] == False
assert ['topic', 'Health'] in spec['required']['tc']
assert ['topic', 'Politics & Government'] in spec['required']['tc']
assert spec['action'] == 'block'
print('TopicScanner OK')

# ToxicityScanner
tox = ToxicityScanner(labels=[ToxicityLabel.HATE_SPEECH])
spec2 = tox.to_guardline_spec()
assert ['hate_speech', 'hate speech'] in spec2['required']['tc']
print('ToxicityScanner OK')

# CyberScanner
cyber = CyberScanner(labels=[CyberLabel.MALWARE_ATTACKS])
spec3 = cyber.to_guardline_spec()
assert ['cysecbench', 'Malware Attacks'] in spec3['required']['tc']
print('CyberScanner OK')

# JailbreakScanner
jb = JailbreakScanner(labels=[JailbreakLabel.JAILBREAK])
spec4 = jb.to_guardline_spec()
assert ['jailbreak', 'jailbreak'] in spec4['required']['tc']
print('JailbreakScanner OK')

# ALL sentinel
topic_all = TopicScanner(labels=[TopicLabel.ALL])
spec5 = topic_all.to_guardline_spec()
assert len(spec5['required']['tc']) == 30, f'Expected 30 topic labels, got {len(spec5[\"required\"][\"tc\"])}'
print('ALL sentinel OK')

# Threshold
topic_thresh = TopicScanner(labels=[TopicLabel.HEALTH], threshold=0.8)
spec6 = topic_thresh.to_guardline_spec()
assert spec6.get('threshold') == 0.8
print('Threshold OK')

# Empty labels raises
try:
    TopicScanner(labels=[])
    assert False, 'Should raise ValueError'
except ValueError:
    pass
print('Empty labels validation OK')

# Enum string values match server
assert str(TopicLabel.HEALTH) == 'Health'
assert str(ToxicityLabel.HATE_SPEECH) == 'hate speech'
assert str(CyberLabel.MALWARE_ATTACKS) == 'Malware Attacks'
assert str(JailbreakLabel.JAILBREAK) == 'jailbreak'
print('Enum string values OK')

print('All TC scanner assertions passed')
"
```
  </verify>
  <done>All four TC scanners implemented with correct label enum values matching server's label_config.json exactly. Each scanner maps to its TC head via _TC_HEAD class constant. ALL sentinel expands to all labels except ALL itself. Threshold is included in spec when set. Empty labels raise ValueError.</done>
</task>

<task type="auto">
  <name>Task 2: Wire all scanners into package public API</name>
  <files>
    src/meshulash_guard/scanners/__init__.py
    src/meshulash_guard/__init__.py
  </files>
  <action>
**1. Update `src/meshulash_guard/scanners/__init__.py`:**

Replace the current stub with full re-exports:

```python
"""Scanner implementations for meshulash-guard.

Import scanners and their label enums:
    from meshulash_guard.scanners import PIIScanner, PIILabel
    from meshulash_guard.scanners import TopicScanner, TopicLabel
"""

from .pii import PIIScanner, PIILabel
from .topic import TopicScanner, TopicLabel
from .toxicity import ToxicityScanner, ToxicityLabel
from .cyber import CyberScanner, CyberLabel
from .jailbreak import JailbreakScanner, JailbreakLabel

__all__ = [
    "PIIScanner", "PIILabel",
    "TopicScanner", "TopicLabel",
    "ToxicityScanner", "ToxicityLabel",
    "CyberScanner", "CyberLabel",
    "JailbreakScanner", "JailbreakLabel",
]
```

**2. Update `src/meshulash_guard/__init__.py`:**

Add scanner re-exports to the top-level package for convenience imports. Add after the existing imports:

```python
from .scanners import (
    PIIScanner, PIILabel,
    TopicScanner, TopicLabel,
    ToxicityScanner, ToxicityLabel,
    CyberScanner, CyberLabel,
    JailbreakScanner, JailbreakLabel,
)
```

And extend `__all__` to include all scanner names and label enums.

Keep all existing imports (Guard, ScanResult, ScannerResult, Detection, Action, Condition, exceptions). Just ADD the scanner re-exports.
  </action>
  <verify>
```bash
cd /Users/shalev/Desktop/meshulash_guard && .venv/bin/python -c "
# Test imports from scanners subpackage
from meshulash_guard.scanners import (
    PIIScanner, PIILabel,
    TopicScanner, TopicLabel,
    ToxicityScanner, ToxicityLabel,
    CyberScanner, CyberLabel,
    JailbreakScanner, JailbreakLabel,
)
print('Subpackage imports OK')

# Test imports from top-level package
from meshulash_guard import (
    Guard,
    PIIScanner, PIILabel,
    TopicScanner, TopicLabel,
    ToxicityScanner, ToxicityLabel,
    CyberScanner, CyberLabel,
    JailbreakScanner, JailbreakLabel,
    Action, Condition,
    ScanResult, ScannerResult, Detection,
)
print('Top-level imports OK')

# Verify __all__ includes scanners
import meshulash_guard
assert 'PIIScanner' in meshulash_guard.__all__
assert 'TopicScanner' in meshulash_guard.__all__
assert 'PIILabel' in meshulash_guard.__all__
print('__all__ includes scanners')

# Test direct module imports still work
from meshulash_guard.scanners.pii import PIIScanner as PS
from meshulash_guard.scanners.topic import TopicScanner as TS
print('Direct module imports OK')

print('All package wiring assertions passed')
"
```
  </verify>
  <done>All five scanner classes and their label enums are importable from both meshulash_guard.scanners (subpackage) and meshulash_guard (top-level). __all__ includes all scanner names. Direct module imports continue to work. Existing Guard, ScanResult, Action, Condition imports are unaffected.</done>
</task>

</tasks>

<verification>
1. All 5 scanners importable from `meshulash_guard.scanners`
2. All 5 scanners importable from `meshulash_guard` (top-level)
3. Each TC scanner's `to_guardline_spec()` produces correct `[head, label]` pairs
4. TC label enum values exactly match server's `label_config.json` strings
5. ALL sentinel works for all TC scanners
6. `from meshulash_guard import Guard, PIIScanner, TopicScanner, Action` works in one line
7. Existing imports (Guard, ScanResult, etc.) are not broken
</verification>

<success_criteria>
- TopicScanner, ToxicityScanner, CyberScanner, JailbreakScanner are all importable with their scoped label enums
- Each TC scanner hides its head name behind the _TC_HEAD constant
- Label enum values are the exact strings from the server's label_config.json
- scanners/__init__.py re-exports all 5 scanners + 5 label enums
- __init__.py re-exports scanners at package top level
- All verification commands pass
</success_criteria>

<output>
After completion, create `.planning/phases/02-scanners/02-02-SUMMARY.md`
</output>
