Metadata-Version: 2.5
Name: s-skillgate
Version: 0.1.7
Summary: Pre-publish gate for Skills Hub skills: secrets, internal-info denylist, manifest validation
Author: Dmitry
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# s-skillgate

> Pre-publish gate для навыков Skills Hub: секреты + денилист внутренней инфы + валидация манифеста (zero-deps Python)

## Что это

«Охранник» (pre-publish gate), который проверяет папку навыка перед публикацией на три вещи:

1. **Секреты/токены** — `glpat-`, `ghp_`, `sk-`, `AKIA`, JWT, приватные ключи, DSN с паролем.
2. **Внутренняя инфа** — локальные пути, публичные IP (warn) + собственная конкретика через приватный оверлей.
3. **Структура/манифест** — обязательные поля `SKILL.md`, инвариант «tooling ⇒ есть cli/mcp», совпадение объявленного `name` с именем каталога навыка.

Возвращает вердикт `ok` / `warn` / `fail` и exit-code `0/1/2`. Zero-deps, только stdlib Python ≥3.11 — работает везде, включая Windows без установки бинарей.

## Установка

```sh
pip install s-skillgate
# или без установки:
uvx --from s-skillgate skillgate run path/to/skill
```

## Запуск

```sh
skillgate run path/to/skill           # человекочитаемо
skillgate run path/to/skill --json    # машинно; exit 0 ok / 1 warn / 2 fail
skillgate sweep                       # прогон по ~/.claude/skills: имя ≠ каталог
skillgate sweep <dir> --json --all    # свой каталог навыков, машинно, со всеми
```

### Имя навыка против каталога

Объявленное во фронтматтере `name` обязано совпадать с именем каталога. Расхождение
не ломает вызов (резолвер ключует по каталогу), но канал установки читает именно
`name` — навык встаёт под чужим slug, а ссылки на него ведут в пустоту. Поэтому
уровень `fail`, и вердикт называет обе стороны: «каталог `graphify`, объявлено
`graphify-windows`». `sweep` показывает такие расхождения по всему каталогу навыков
списком, заодно называя каталоги без `SKILL.md`. junction не разыменовывается —
имя навыка это имя точки монтирования. Плагинный навык (короткое `name`, а форму
`plugin:skill` собирает харнесс) ложной тревоги не даёт.

Как библиотека:

```python
from skillgate import run_gate, sweep_skills

report = run_gate("path/to/skill")
print(report.verdict, report.exit_code)

for entry in sweep_skills():          # по умолчанию ~/.claude/skills
    if not entry.ok:
        print(entry.directory, [f.message for f in entry.findings])
```

## Правила

- **Общие паттерны** (токены, пути, IP) — `src/skillgate/data/denylist.toml`. Один файл, формат TOML, regex в literal-строках (одинарные кавычки). Дописал `[[rule]]` — правило работает.
- **Исключения/заглушки** (публичные домены-примеры, `__set_me__`, RFC1918) — `src/skillgate/data/allowlist.toml`. Если совпадение попало под allowlist — находка гасится.
- **Приватная конкретика** (реальные IP серверов, имена приватных git-групп, внутренние домены) — НЕ в публичном пакете. Скопируй `data/denylist.local.example.toml` → `data/denylist.local.toml` (он в `.gitignore`) и впиши свои значения; `run_gate` подхватит оверлей автоматически.

Правила покрыты golden-тестами (`tests/golden/`): `must_match.txt` (обязано ловиться) / `must_not_match.txt` (легальные заглушки). Менять правило → добавить кейс.

## Тесты

```sh
uv run --with pytest pytest -q
```

## License

MIT
