Metadata-Version: 2.5
Name: s-agentskit
Version: 0.2.0
Summary: AI-агенты как сущности: автономный онбординг инструкций (идемпотентные managed-блоки в .claude/.cursor/.codex/.agents/.github/…) и хуки агента поверх единого реестра агентов-данных. Тонкий слой на clikit.
Author: Dmitry
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: s-clikit
Provides-Extra: dev
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# agentskit

**AI-агенты как сущности: автономный онбординг инструкций.** Переиспользуемый
слой (dist `s-agentskit` / import `agentskit`) поверх `clikit`. Делает любой
навык/инструмент **автономным онбордером**: он сам прописывает свои инструкции
(managed-блок) в конфиг-файлы любого AI-агента — `.claude` / `.cursor` / `.codex`
/ `.agents` / `.github` / … — идемпотентно и аддитивно (чужой текст не затирается),
а также **регистрирует свои хуки** в настройках агента, не задевая чужие.

```
agentskit (реестр агентов = данные) → onboard(namespace, body) → managed-блок в N агентов
```

## Зачем

Знание «где живёт каждый AI-агент» (его config-папка и memory-файл) не должно
дублироваться в каждом инструменте. agentskit держит **единый реестр агентов как
ДАННЫЕ** (`data/agents.json`, 17 агентов) — потребитель приносит только контент
(тело инструкции + свой namespace), а механизм (детект, инъекция, резолв
global/project) общий.

> Роль «агент-как-адаптер» (драйв агента из комбайна gateway/bublictr) — зона
> **adapterkit**, не этого кита. agentskit отвечает только за онбординг.

## Установка

```bash
pip install s-agentskit       # import agentskit  (dist-имя ≠ import-имя)
# dev:  uv sync --extra dev
```

## Библиотека (основной способ)

```python
from agentskit import onboard, resolve_agent_keys

onboard(
    namespace="atlas",                       # маркеры <!-- ATLAS:BEGIN/END -->
    body="## Работай в Atlas\n- atlas task …",
    scope="all",                             # global | repo | all
    agents=resolve_agent_keys("claude,cursor"),  # или None — все существующие файлы
    create=True,
)
```

Идемпотентно: повторный вызов с тем же `body` → `unchanged`. Несколько плагинов
(разные `namespace`) сосуществуют в одном `CLAUDE.md` без коллизий.

## CLI

```bash
agentskit agents                  # весь реестр агентов (+ шаблон пути навыка)
agentskit detect                  # какие агенты есть в проекте/$HOME
agentskit skill-path notebooklm --agents agy   # куда лёг бы навык (без записи)
agentskit skill-path notebooklm --agents agy --legacy --stale  # где навык лежит по-старому
agentskit onboard -n mytool --body-file INSTRUCTIONS.md --agents claude,cursor --create
agentskit uninstall -n mytool --agents all
agentskit hook install -n mytool -c "mytool session-hook" --matcher "startup|resume"
agentskit hook status -n mytool          # стоит ли наш хук и сколько чужих рядом
agentskit hook uninstall -n mytool       # снять только наш
```

## Публичный API

`onboard` / `uninstall` · `detect_agents` / `detect_dirs_of` / `list_agents` ·
`resolve_agent_keys` · `resolve_targets` · `skill_path_for` / `skill_root_for` /
`resolve_skill_targets` · `skill_paths_for` / `skill_roots_for` /
`skill_layout_for` · `Evidence` / `skill_evidence_for`
· `readonly_skill_paths_for` / `resolve_readonly_skill_targets`
· `legacy_skill_paths_for` / `resolve_legacy_skill_targets`
· `inject_managed_block` / `has_managed_block` /
`strip_managed_block` / `managed_block` · `begin_marker` / `end_marker` ·
`install_hook` / `uninstall_hook` / `hook_status` / `hook_settings_path` /
`supports_hooks` / `merge_hook` / `remove_hook` · `HookSpec` / `SettingsIO` ·
`AgentSpec` / `SkillLayout` / `HookLayout` / `register_agent_spec` / `agent_registry`.

## Раскладка навыков (`skill_layout`)

Путь навыка = `<base>/<config_dir>/<subdir…>/<filename>`, `base` — корень проекта
или `$HOME`. Две формы выражаются одним полем `subdir`:

| форма | пример | агенты |
|---|---|---|
| каталог на навык | `<config_dir>/skills/<name>/SKILL.md` | claude, antigravity, codex, opencode |
| плоский файл | `<config_dir>/<name>.md` | cursor, kiro, … |

Форма и папка — разные вещи: `config_dir` у каждого агента свой (`.claude` у
claude, `.gemini/config` у antigravity, `.agents` у codex, `.config/opencode` у
opencode), а форма «каталог + `SKILL.md`» у них общая.

`global_install` / `project_install` — в каких скоупах раскладка **действует**
(опт-ин, чтобы включение у одного агента не начало сыпать файлы в `$HOME` у
остальных). Разделены осознанно: глобальный и рабочий корни у агента бывают
**разными папками** — у antigravity доказанный глобальный `~/.gemini/config/skills`.
`skill_layout.config_dir` переопределяет папку агента для конкретной раскладки.
Дополнительные места, откуда агент **тоже** читает навыки, —
`extra_skill_layouts` (`skill_paths_for` / `skill_roots_for` дают их списком,
чтобы искать уже поставленный навык, а не класть ещё одну копию: opencode читает
5 корней). Списки перечисляют и read-only места: первый элемент **не обязан**
быть местом записи — куда пишем, отвечают `skill_root_for` / `skill_path_for`
(у antigravity в проекте — `None`).

`installable` (по умолчанию `true`) — является ли раскладка **местом записи**.
`false` = каталог называем (поиск уже лежащего, отчёты, `skill-path`), но кит
туда **не ставит**. Так помечен проектный `.agents/skills` у antigravity: его
заявляет IDE, а `agy --print` его не читает (проба DELTA). Раньше он молча
становился «первой подходящей» раскладкой проектного скоупа — и онбординг клал
навык в каталог, куда агент не смотрит. Инвариант под тестом: у агента, чьи
пути доказаны живым прогоном, раскладка без улики **не может** быть
`installable` — `AgentSpec` отвергает такую запись реестра с ошибкой. Итог:
`skill_layout_for` / `skill_path_for` / `resolve_skill_targets` отдают только
места записи (у agy в проекте — честное «некуда»), а читаемые каталоги
перечисляют `skill_paths_for` / `readonly_skill_paths_for` /
`resolve_readonly_skill_targets`.

Имя навыка — плоский slug: разделители пути, `..` и пустое имя отклоняются
(`ValueError`), иначе каталог навыка уводит запись из папки агента.

### Доказательность (`evidence`)

У записи агента и у **каждой** раскладки есть машиночитаемая улика — откуда это
известно:

| kind | значит | обязательные поля |
|---|---|---|
| `empirical` | доказано живым прогоном агента | `date` + `method` + `source` |
| `docs` | прочитано в документации, живьём не проверено | `source` (ссылка) |
| `unverified` | не проверено ничем (дефолт) | — |

```python
from agentskit import get_agent_spec, skill_evidence_for

ev = skill_evidence_for(get_agent_spec("antigravity"), scope="global")
ev.proven, ev.date        # → True, "2026-07-21"
```

Улика едет вместе с путём (`Target.evidence`) и печатается CLI
(`agentskit agents`, `agentskit skill-path`) — потребитель обязан иметь
возможность спросить «этот путь доказан?» **до** записи на диск. Поле заведено
2026-07-21 после серии ошибок одного класса: реестр не отличал «прочитано в
документации» от «кто-то предположил», и установщик рапортовал успех в каталог,
куда агент не смотрит.

Что доказано живым прогоном 2026-07-21 (навыки-пробы с кодовым словом +
`agy --print` / `opencode run`):

| агент | ставим | тоже читает | НЕ читает |
|---|---|---|---|
| antigravity | `~/.gemini/config/skills/<name>/SKILL.md` — и всё: в проектном скоупе ставить **некуда** | — (рабочий `.agents/skills` числится read-only: только поиск) | `~/.agents/skills`, `~/.gemini/skills`, рабочие `.agents/skills` и `.gemini/skills`, плоский `<name>.md` |
| opencode | `~/.config/opencode/skills/<name>/SKILL.md`, проект `.opencode/skills/<name>/SKILL.md` | `~/.opencode/skills`, `~/.agents/skills`, рабочий `.agents/skills` | `~/.codex/skills` |
| codex | `~/.agents/skills/<name>/SKILL.md`, проект `.agents/skills/<name>/SKILL.md` (**по документации**) | — | — |

Колонка «ставим» сверяется с реестром **тестом**
(`tests/test_docs_match_registry.py`), а не глазами: разъехаться справке и коду
второй раз нельзя. Там же (`tests/test_prose_matches_registry.py`) сверяются
**все числа о реестре** в этом README и в докстрингах кита: число в прозе либо
выводится из реестра тестом, либо его в прозе нет — иначе текст снова начинает
жить своей жизнью.

### Устаревшие раскладки (`legacy_skill_layouts`)

Смена раскладки не стирает то, что уже лежит у людей на дисках, поэтому реестр
помнит и старые места агента — но только чтобы их НАЗВАТЬ
(`resolve_legacy_skill_targets(..., existing_only=True)`, `agentskit skill-path
… --legacy --stale`). Кит по ним ничего не пишет и не удаляет: перенос и уборку
делает человек. Заведены у трёх агентов, чьи пути исправил опыт 2026-07-21:
antigravity (`~/.agents/skills` — куда кит ошибочно ставил глобальные навыки,
`~/.gemini/skills`, плоский `.agents/<name>.md`), codex (`.codex/<name>.md` и
`.codex/skills/<name>/SKILL.md` — писал skillkit), opencode
(`.opencode/<name>.md`). Все они читаются агентами **не** — потому и легаси.

## Хуки агента (`AgentSpec.hooks`)

Хук — команда, которую **агент сам зовёт** на своё событие (`SessionStart` у
Claude Code: впрыснуть контекст в начало сессии). Механизм описан в реестре
теми же данными, что и раскладка навыков: файл настроек, формат, известные
события, env-переопределение корня. У большинства агентов хуков нет — реестр
честно отвечает «нет» (`supports_hooks("codex") is False`), и установка отдаёт
`skipped`, а не падает.

```python
from agentskit import HookSpec, install_hook

install_hook(HookSpec(
    namespace="atlas",                    # своя примета, как у managed-блока
    command="atlas session-hook",         # ВСТРОЕННАЯ команда CLI, не файл-скрипт
    event="SessionStart",
    matcher="startup|resume",
    timeout=15,
    markers=("atlas", "session_atlas.py"),  # + старые команды: миграция без дублей
))
```

Свойства (под тестами): **идемпотентность** (повтор → `unchanged`, файл байт в
байт), **чужие хуки/группы/события/ключи не трогаются**, **uninstall снимает
только наш** и не оставляет пустышек, **dry-run** показывает готовый текст и
ничего не пишет, **битые настройки** → честная ошибка вместо затирания,
**запись атомарна** (обрыв не оставляет обрубка конфига).

Что считается «нашим»: точное совпадение команды → явные `markers`
(подстрокой — туда кладут старые команды при миграции) → `namespace` **целым
словом**. Целым словом, а не подстрокой, — иначе чужие `atlassian-sync` /
`myatlas-tool` / `atlas_backup.sh` попали бы под снос вместе с нашим хуком.

Команда хука — встроенная команда CLI плагина, а не файл-скрипт: она не зависит
от `python`/`python3` в PATH (uv-tool его не кладёт) и версионируется вместе с
инструментом.

Внешние эффекты инъектируются: `home=` / `cwd=` / `env=` задают путь настроек,
`io=SettingsIO(read=…, write=…, exists=…)` — доступ к файлу. Заявку можно
принести словарём манифеста: `HookSpec.from_manifest({"event": …, "command": …})`.

## Расширение реестра

Добавить агента без правки кита: запись в `data/agents.json`, либо
`register_agent_spec(AgentSpec(...))`, либо entry-points группа
`agentskit.agent_specs` во внешнем пакете. Хуки нового агента — тем же способом
(`HookLayout`), кода движка это не касается.

Агента `gemini` (Gemini CLI) в реестре **нет** с 2026-07-21: CLI закрыт, все
перешли на Antigravity. Каталог `~/.gemini` при этом живой — это дом самого
Antigravity, и доказанный корень его навыков лежит именно там
(`~/.gemini/config/skills`). Убран агент, а не папка: владеет ею `antigravity`,
и никто другой объявлять пути в `~/.gemini` не вправе (под тестом).

## Режимы

- **reference** (MVP): managed-блок-указатель в memory-файл агента (`CLAUDE.md` и т.п.).
- **full** (фаза 2): материализация контента целиком в per-agent layout (как
  `uipro`-инсталлеры).

## Лицензия

[MIT](LICENSE).
