s-skillkit 0.2.2__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/.gitignore +48 -48
- s_skillkit-0.3.2/.gitlab-ci.yml +31 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/AGENTS.md +45 -45
- s_skillkit-0.3.2/CHANGELOG.md +329 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/PKG-INFO +54 -5
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/README.md +53 -4
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/pyproject.toml +1 -1
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/__init__.py +26 -4
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/binary_installer.py +308 -308
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/bootstrap.py +234 -234
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/collections.py +179 -179
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/config.py +123 -123
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/deps_installer.py +297 -297
- s_skillkit-0.3.2/skillkit/errors.py +60 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/filter.py +258 -258
- s_skillkit-0.3.2/skillkit/hook_register.py +357 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/__init__.py +1099 -943
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/differ.py +44 -44
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/fetchers.py +101 -99
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/fsops.py +212 -198
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/meta.py +256 -175
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/installer/models.py +192 -171
- s_skillkit-0.3.2/skillkit/installer/ownership.py +278 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/linker.py +124 -124
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/manifest.py +356 -293
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/mcp_register.py +238 -229
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/path_store.py +394 -394
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/paths.py +55 -55
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/project.py +120 -120
- s_skillkit-0.3.2/skillkit/targets/__init__.py +61 -0
- s_skillkit-0.3.2/skillkit/targets/antigravity.py +129 -0
- s_skillkit-0.3.2/skillkit/targets/base.py +229 -0
- s_skillkit-0.3.2/skillkit/targets/claude_code.py +46 -0
- s_skillkit-0.3.2/skillkit/targets/codex.py +74 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/targets/detect.py +29 -26
- s_skillkit-0.3.2/skillkit/targets/opencode.py +53 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/tooling.py +387 -321
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/uv_bootstrap.py +178 -178
- s_skillkit-0.3.2/tests/test_agent_skill_layouts_2026_07_21.py +452 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_binary_installer.py +265 -265
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_bootstrap.py +233 -233
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_deps_binary.py +218 -218
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_deps_installer.py +202 -202
- s_skillkit-0.3.2/tests/test_destructive_ownership_gate_2026_07_21.py +684 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_errors.py +102 -98
- s_skillkit-0.3.2/tests/test_hooks.py +465 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_installer.py +9 -2
- s_skillkit-0.3.2/tests/test_legacy_paths_no_orphans_2026_07_21.py +368 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_manifest.py +185 -185
- s_skillkit-0.3.2/tests/test_packaging_lock.py +37 -0
- s_skillkit-0.3.2/tests/test_remove_ownership_guard_2026_07_21.py +275 -0
- s_skillkit-0.3.2/tests/test_targets.py +113 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_tooling.py +356 -356
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_typed_fields.py +75 -75
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_uv_bootstrap.py +122 -122
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/uv.lock +1 -1
- s_skillkit-0.2.2/CHANGELOG.md +0 -73
- s_skillkit-0.2.2/skillkit/errors.py +0 -33
- s_skillkit-0.2.2/skillkit/targets/__init__.py +0 -21
- s_skillkit-0.2.2/skillkit/targets/antigravity.py +0 -18
- s_skillkit-0.2.2/skillkit/targets/base.py +0 -97
- s_skillkit-0.2.2/skillkit/targets/claude_code.py +0 -21
- s_skillkit-0.2.2/skillkit/targets/codex.py +0 -21
- s_skillkit-0.2.2/tests/test_targets.py +0 -58
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/LICENSE +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/skillkit/pkg_manager.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/conftest.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_collections.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_config.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_filter.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_git_install.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_mcp_register.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_path_store.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_paths.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_pkg_manager.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_project.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_stub_guard.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_transactional_install.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_traversal_guard.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_w268_subdir_skill.py +0 -0
- {s_skillkit-0.2.2 → s_skillkit-0.3.2}/tests/test_w272_empty_manifest_guard.py +0 -0
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
# === atlas universal gitignore ===
|
|
2
|
-
|
|
3
|
-
# OS / IDE
|
|
4
|
-
.DS_Store
|
|
5
|
-
Thumbs.db
|
|
6
|
-
.vscode/
|
|
7
|
-
.idea/
|
|
8
|
-
*.swp
|
|
9
|
-
*.swo
|
|
10
|
-
|
|
11
|
-
# Sensitive
|
|
12
|
-
.env
|
|
13
|
-
.env.local
|
|
14
|
-
*.key
|
|
15
|
-
*.pem
|
|
16
|
-
secrets/
|
|
17
|
-
private/
|
|
18
|
-
|
|
19
|
-
# Python
|
|
20
|
-
__pycache__/
|
|
21
|
-
*.py[cod]
|
|
22
|
-
.venv/
|
|
23
|
-
venv/
|
|
24
|
-
.pytest_cache/
|
|
25
|
-
.ruff_cache/
|
|
26
|
-
*.egg-info/
|
|
27
|
-
|
|
28
|
-
# Node / JS
|
|
29
|
-
node_modules/
|
|
30
|
-
.next/
|
|
31
|
-
dist/
|
|
32
|
-
build/
|
|
33
|
-
|
|
34
|
-
# Temporary / large
|
|
35
|
-
*.log
|
|
36
|
-
*.tmp
|
|
37
|
-
nul
|
|
38
|
-
NUL
|
|
39
|
-
*.zip
|
|
40
|
-
*.rar
|
|
41
|
-
*.7z
|
|
42
|
-
|
|
43
|
-
# Media (selectively unignore via !path/*.ext if needed for fixtures)
|
|
44
|
-
*.mp4
|
|
45
|
-
*.mov
|
|
46
|
-
*.avi
|
|
47
|
-
*.mkv
|
|
1
|
+
# === atlas universal gitignore ===
|
|
2
|
+
|
|
3
|
+
# OS / IDE
|
|
4
|
+
.DS_Store
|
|
5
|
+
Thumbs.db
|
|
6
|
+
.vscode/
|
|
7
|
+
.idea/
|
|
8
|
+
*.swp
|
|
9
|
+
*.swo
|
|
10
|
+
|
|
11
|
+
# Sensitive
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
*.key
|
|
15
|
+
*.pem
|
|
16
|
+
secrets/
|
|
17
|
+
private/
|
|
18
|
+
|
|
19
|
+
# Python
|
|
20
|
+
__pycache__/
|
|
21
|
+
*.py[cod]
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
|
|
28
|
+
# Node / JS
|
|
29
|
+
node_modules/
|
|
30
|
+
.next/
|
|
31
|
+
dist/
|
|
32
|
+
build/
|
|
33
|
+
|
|
34
|
+
# Temporary / large
|
|
35
|
+
*.log
|
|
36
|
+
*.tmp
|
|
37
|
+
nul
|
|
38
|
+
NUL
|
|
39
|
+
*.zip
|
|
40
|
+
*.rar
|
|
41
|
+
*.7z
|
|
42
|
+
|
|
43
|
+
# Media (selectively unignore via !path/*.ext if needed for fixtures)
|
|
44
|
+
*.mp4
|
|
45
|
+
*.mov
|
|
46
|
+
*.avi
|
|
47
|
+
*.mkv
|
|
48
48
|
|
|
49
49
|
# git worktrees (агентские)
|
|
50
|
-
.worktrees/
|
|
50
|
+
.worktrees/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Автопубликация на PyPI по семвер-тегу vX.Y.Z через Trusted Publishing (OIDC).
|
|
2
|
+
# Токены НИГДЕ не хранятся: GitLab выдаёт короткоживущий OIDC-токен (PYPI_ID_TOKEN),
|
|
3
|
+
# он одноразово обменивается на PyPI API-токен. Издатель настраивается на pypi.org:
|
|
4
|
+
# Manage -> Publishing -> GitLab (namespace=S-kits, project=skillkit,
|
|
5
|
+
# top-level pipeline file=.gitlab-ci.yml, environment — пусто).
|
|
6
|
+
# Запускается ТОЛЬКО на тег vX.Y.Z (обычный push на main ничего не публикует).
|
|
7
|
+
stages: [publish]
|
|
8
|
+
|
|
9
|
+
publish-pypi:
|
|
10
|
+
stage: publish
|
|
11
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm
|
|
12
|
+
rules:
|
|
13
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
14
|
+
id_tokens:
|
|
15
|
+
PYPI_ID_TOKEN:
|
|
16
|
+
aud: pypi
|
|
17
|
+
script:
|
|
18
|
+
- uv build
|
|
19
|
+
# обмен GitLab OIDC -> одноразовый PyPI API-токен (Trusted Publishing):
|
|
20
|
+
- |
|
|
21
|
+
export UV_PUBLISH_TOKEN=$(python3 - <<'PY'
|
|
22
|
+
import json, os, urllib.request
|
|
23
|
+
req = urllib.request.Request(
|
|
24
|
+
"https://pypi.org/_/oidc/mint-token",
|
|
25
|
+
data=json.dumps({"token": os.environ["PYPI_ID_TOKEN"]}).encode(),
|
|
26
|
+
headers={"Content-Type": "application/json"},
|
|
27
|
+
)
|
|
28
|
+
print(json.load(urllib.request.urlopen(req))["token"])
|
|
29
|
+
PY
|
|
30
|
+
)
|
|
31
|
+
- uv publish
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
# AGENTS.md — skillkit
|
|
2
|
-
|
|
3
|
-
> Контекст для AI-ассистентов (Claude Code, ChatGPT, Cursor и т.п.), работающих
|
|
4
|
-
> над этим проектом.
|
|
5
|
-
|
|
6
|
-
## Что это
|
|
7
|
-
|
|
8
|
-
(заполнить one-line)
|
|
9
|
-
|
|
10
|
-
## Atlas
|
|
11
|
-
|
|
12
|
-
Проект зарегистрирован в Atlas-БД (Atlas). Карточка:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
atlas projects get skillkit
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Любые изменения метаданных (приоритет, статус, теги) — через atlas CLI:
|
|
19
|
-
|
|
20
|
-
- `atlas projects update skillkit --priority P0` — поменять приоритет
|
|
21
|
-
- `atlas add-tags skillkit -t domain:<slug>` — добавить тег
|
|
22
|
-
- `atlas projects move skillkit --to-type <type>` — конвертировать тип
|
|
23
|
-
|
|
24
|
-
## Тип / Статус (на момент создания)
|
|
25
|
-
|
|
26
|
-
- type=`kit`, status=`experiment`, priority=`P1`
|
|
27
|
-
|
|
28
|
-
## Правила работы
|
|
29
|
-
|
|
30
|
-
- Все исходные тексты, документы, код проекта — в этом репо.
|
|
31
|
-
- Чувствительные данные (`.env`, токены, ключи) — игнорируются `.gitignore`.
|
|
32
|
-
- AI-ассистенту разрешено: читать, генерировать, редактировать в этом репо.
|
|
33
|
-
|
|
34
|
-
## Канонические команды
|
|
35
|
-
|
|
36
|
-
- `atlas projects get skillkit` — карточка проекта
|
|
37
|
-
- `atlas task list --project skillkit` — задачи проекта (когда W7
|
|
38
|
-
волна будет реализована)
|
|
39
|
-
|
|
40
|
-
<!-- atlas:usage:start -->
|
|
41
|
-
## Управление проектом — через Atlas
|
|
42
|
-
|
|
43
|
-
Этот проект ведётся в Atlas (личная PM-система портфеля). Для задач/проектов/эпиков/бэкапов
|
|
44
|
-
используй CLI `atlas` и вызывай навык `atlas` — вся логика и роутинг внутри навыка.
|
|
45
|
-
<!-- atlas:usage:end -->
|
|
1
|
+
# AGENTS.md — skillkit
|
|
2
|
+
|
|
3
|
+
> Контекст для AI-ассистентов (Claude Code, ChatGPT, Cursor и т.п.), работающих
|
|
4
|
+
> над этим проектом.
|
|
5
|
+
|
|
6
|
+
## Что это
|
|
7
|
+
|
|
8
|
+
(заполнить one-line)
|
|
9
|
+
|
|
10
|
+
## Atlas
|
|
11
|
+
|
|
12
|
+
Проект зарегистрирован в Atlas-БД (Atlas). Карточка:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
atlas projects get skillkit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Любые изменения метаданных (приоритет, статус, теги) — через atlas CLI:
|
|
19
|
+
|
|
20
|
+
- `atlas projects update skillkit --priority P0` — поменять приоритет
|
|
21
|
+
- `atlas add-tags skillkit -t domain:<slug>` — добавить тег
|
|
22
|
+
- `atlas projects move skillkit --to-type <type>` — конвертировать тип
|
|
23
|
+
|
|
24
|
+
## Тип / Статус (на момент создания)
|
|
25
|
+
|
|
26
|
+
- type=`kit`, status=`experiment`, priority=`P1`
|
|
27
|
+
|
|
28
|
+
## Правила работы
|
|
29
|
+
|
|
30
|
+
- Все исходные тексты, документы, код проекта — в этом репо.
|
|
31
|
+
- Чувствительные данные (`.env`, токены, ключи) — игнорируются `.gitignore`.
|
|
32
|
+
- AI-ассистенту разрешено: читать, генерировать, редактировать в этом репо.
|
|
33
|
+
|
|
34
|
+
## Канонические команды
|
|
35
|
+
|
|
36
|
+
- `atlas projects get skillkit` — карточка проекта
|
|
37
|
+
- `atlas task list --project skillkit` — задачи проекта (когда W7
|
|
38
|
+
волна будет реализована)
|
|
39
|
+
|
|
40
|
+
<!-- atlas:usage:start -->
|
|
41
|
+
## Управление проектом — через Atlas
|
|
42
|
+
|
|
43
|
+
Этот проект ведётся в Atlas (личная PM-система портфеля). Для задач/проектов/эпиков/бэкапов
|
|
44
|
+
используй CLI `atlas` и вызывай навык `atlas` — вся логика и роутинг внутри навыка.
|
|
45
|
+
<!-- atlas:usage:end -->
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Все заметные изменения кита `s-skillkit` фиксируются здесь. Формат близок к
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/ru/1.1.0/); версионирование —
|
|
5
|
+
[SemVer](https://semver.org/lang/ru/).
|
|
6
|
+
|
|
7
|
+
## [0.3.1] — 2026-07-21
|
|
8
|
+
|
|
9
|
+
Круг 3 независимой проверки нашёл два настоящих дефекта в правках 0.3.0 и один
|
|
10
|
+
того же класса рядом. Все три — про уничтожение чужого, которого кит не ставил.
|
|
11
|
+
|
|
12
|
+
### Fixed — гейт принадлежности стал свойством ОПЕРАЦИИ, а не команды
|
|
13
|
+
|
|
14
|
+
- **ДЕФЕКТ A — обход гейта через `migrate_scope`.** В 0.3.0 проверка «удаляем
|
|
15
|
+
только своё» стояла внутри `remove`. Гейт, привязанный к КОМАНДЕ, обходила
|
|
16
|
+
соседняя команда: `migrate_scope` звала `_link_into_scope(..., force=True)`, а
|
|
17
|
+
тот безусловным `_force_rmtree` сносил каталог, стоящий на месте будущей
|
|
18
|
+
ссылки. Воспроизведено: наша copy в `~/.codex/skills/notebooklm` + ЧУЖОЙ
|
|
19
|
+
`~/.agents/skills/notebooklm` → после `migrate_scope` чужой payload уничтожен,
|
|
20
|
+
канон заменён junction'ом. Лечение — не затыкание `migrate_scope`, а сведение
|
|
21
|
+
ВСЕГО разрушения к одному входу `skillkit.installer.ownership.PathGuard`,
|
|
22
|
+
который сам проверяет улику принадлежности. `migrate_scope` больше не
|
|
23
|
+
подставляет `force`: занятый чужим канон отменяет миграцию навыка целиком (в
|
|
24
|
+
`failed`), обе стороны на диске целы. Обойти гейт нельзя ПО ПОСТРОЕНИЮ:
|
|
25
|
+
разрушение в зоне агента доступно только через `destroy_in_scope` /
|
|
26
|
+
`clear_for_link` / `take_into_store` (каждый зовёт `assert_ours`), в
|
|
27
|
+
собственной зоне кита — через `destroy_in_store` / `swap_in_store` (требуют,
|
|
28
|
+
чтобы путь лежал ВНУТРИ стора). Стережёт архитектурный тест: он читает AST
|
|
29
|
+
пакета и запрещает прямые `_force_rmtree` / `move` / `remove_link` / `os.replace`
|
|
30
|
+
вне узкого списка модулей — новая команда физически не повторит дефект.
|
|
31
|
+
- **ДЕФЕКТ B — осиротевший `~/.antigravity/skills`.** Выпущенный тег `v0.2.1`
|
|
32
|
+
ставил навыки agy в `~/.antigravity/skills` (`git show v0.2.1:…/antigravity.py`
|
|
33
|
+
— `dirname = ".antigravity"`); коммит 067c840 объявил путь legacy, 4d093a2 убрал
|
|
34
|
+
совсем, и на момент круга 3 `AntigravityTarget.legacy_dirnames` был пуст.
|
|
35
|
+
Установки выпущенной версии стали невидимы для `status`, `remove` и
|
|
36
|
+
`migrate_scope`. Путь снова объявлен `legacy_dirnames = (".antigravity",)` —
|
|
37
|
+
это безопасно благодаря гейту (чужих соседей `google-flow` / `notebooklm` /
|
|
38
|
+
`telegram` без нашей меты не сносим). Инвариант «ни одного осиротевшего пути»
|
|
39
|
+
закрыт тестом: таблица `_EVER_WROTE` перечисляет КАЖДУЮ раскладку из истории
|
|
40
|
+
тегов и коммитов, тест требует, чтобы сегодня каждая была каноном или заявленным
|
|
41
|
+
legacy (в т.ч. project-scope наследие `<project>/.antigravity/skills`), и сам
|
|
42
|
+
ловит снятое объявление (`test_the_invariant_actually_catches_a_dropped_legacy`).
|
|
43
|
+
- **Тот же класс дефекта в `mcp_register`.** `_read_config` возвращал `{}` на
|
|
44
|
+
любом не-словаре, и следом `_write_config` клал на место чужого ВАЛИДНОГО, но
|
|
45
|
+
не-объектного конфига (напр. JSON-массива пользователя) наш объект — данные
|
|
46
|
+
исчезали без предупреждения. Теперь такой конфиг даёт `status=error` и остаётся
|
|
47
|
+
нетронутым — как уже вёл себя `hook_register`. Оба писателя настроек агента
|
|
48
|
+
ведут себя одинаково.
|
|
49
|
+
- **`_force_rmtree` стал link-aware**: снимает ссылку КАК ссылку и не идёт
|
|
50
|
+
внутрь. `shutil.rmtree` отсекает только POSIX-symlink, а Windows junction для
|
|
51
|
+
него — обычный каталог, и удаление уходило бы СКВОЗЬ него в цель (стор или
|
|
52
|
+
чужой каталог).
|
|
53
|
+
- **Улика принадлежности — по МАРКЕРУ содержимого меты, а не по ИМЕНИ файла.**
|
|
54
|
+
`is_managed_dir` считал каталог нашим по одному лишь НАЛИЧИЮ `_skill_meta.json`,
|
|
55
|
+
без разбора. Любой посторонний инструмент, положивший файл-тёзку, попадал под
|
|
56
|
+
снос без `force`. Теперь `_build_meta` штампует устойчивый маркер
|
|
57
|
+
(`_skillkit_managed: skillkit/skill-meta/v1`, единая точка `stamp_ownership`),
|
|
58
|
+
и каталог считается нашим ТОЛЬКО по этому маркеру. Оба прежних требования
|
|
59
|
+
удовлетворены сразу: файл-тёзка чужого формата (без маркера) → каталог НЕ наш
|
|
60
|
+
(`ForeignPathError`, снос лишь через `force`); битый JSON вокруг уцелевшего
|
|
61
|
+
маркера — всё равно наш (сырая подпись как неразрушимая улика, порча остальных
|
|
62
|
+
полей неважна). Обратная совместимость для очень старых copy-установок без
|
|
63
|
+
маркера — через вторую независимую улику `PathGuard.is_ours`: ссылка в наш
|
|
64
|
+
стор (стор — доверенная зона). Маркер — версия формата, не бренд: ребрендинг
|
|
65
|
+
`set_managed_by` на него не влияет.
|
|
66
|
+
- **AST-детектор разрушающих вызовов больше не обходится алиасами импортов.**
|
|
67
|
+
Архитектурный тест ловил `shutil.rmtree`/`.unlink`/голые имена, но пропускал
|
|
68
|
+
`import os as o; o.remove(p)`, `from os import remove; remove(p)` и
|
|
69
|
+
`from shutil import rmtree as rt; rt(p)` — то есть сторожил ФОРМУ записи, а не
|
|
70
|
+
инвариант. Детектор теперь разрешает алиасы импортов (`_resolve_aliases`) и
|
|
71
|
+
покрывает полный набор разрушающих имён `os` (`remove`/`unlink`/`rmdir`/
|
|
72
|
+
`replace`/`rename`) и `shutil` (`rmtree`/`move`); безобидные `str.replace` /
|
|
73
|
+
`dataclasses.replace` по-прежнему игнорируются.
|
|
74
|
+
- **Круг 4 — две остаточные асимметрии «чужое → наше».** (1) `has_ownership_marker`
|
|
75
|
+
спускался к сырой подписи и при УСПЕШНО разобранном JSON: чужая мета, где наша
|
|
76
|
+
строка лежала во вложенном поле или в свободном тексте, объявлялась нашей.
|
|
77
|
+
Сырая подпись теперь — ветка ИСКЛЮЧИТЕЛЬНО для не-парсящегося файла; разобравшийся
|
|
78
|
+
JSON решает только ключом верхнего уровня. (2) AST-детектор не видел свои же
|
|
79
|
+
примитивы через модуль (`fsops._force_rmtree`, `ownership.destroy_entry`) —
|
|
80
|
+
ловил их лишь по голому имени; добавлены в ветку атрибутов, три формы обхода
|
|
81
|
+
закреплены контрпримерами.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- `skillkit.installer.ownership` (`PathGuard`, `same_path`) + `ForeignPathError`
|
|
86
|
+
(потомок `ScopeConflict` — прежние `except ScopeConflict` ловят его без правок),
|
|
87
|
+
экспортированы из `skillkit`.
|
|
88
|
+
- Тесты `tests/test_destructive_ownership_gate_2026_07_21.py` — воспроизведение
|
|
89
|
+
ДЕФЕКТА A через `migrate_scope` (до правки чужое гибнет, после — переживает,
|
|
90
|
+
параметризовано по таргетам), гейт на линковке install, `force` как выход,
|
|
91
|
+
«сторовые» методы к scope неприменимы, тест-над-тестом на детектор
|
|
92
|
+
архитектурного правила и защита `mcp_register`. Добавлено: воспроизведение
|
|
93
|
+
улики-по-имени (чужой каталог с файлом-тёзкой `_skill_meta.json` без маркера
|
|
94
|
+
переживает install и remove без `force`), юнит-тесты `is_managed_dir` на
|
|
95
|
+
маркер (нет маркера → не наш; битый JSON с уцелевшим маркером → наш) и
|
|
96
|
+
контрпримеры детектора ровно на три алиасные формы обхода.
|
|
97
|
+
- `stamp_ownership` — публичный экспорт `skillkit` (штамповка маркера
|
|
98
|
+
принадлежности в meta-словарь).
|
|
99
|
+
- Тесты `tests/test_legacy_paths_no_orphans_2026_07_21.py` — инвариант «нет
|
|
100
|
+
осиротевших путей» (дом и project-scope) + доказательство, что он ловит снятое
|
|
101
|
+
объявление.
|
|
102
|
+
|
|
103
|
+
## [0.3.0] — 2026-07-21
|
|
104
|
+
|
|
105
|
+
### Changed (BREAKING) — раскладки навыков приведены к ДОКАЗАННОЙ таблице
|
|
106
|
+
|
|
107
|
+
Основание — живой опыт того же дня: прогоны `agy --print` и `opencode run` с
|
|
108
|
+
навыками-пробами, у каждой пробы своё кодовое слово в `description`; по его
|
|
109
|
+
появлению в ответе видно, прочитан навык или нет. Проба INDIA (`~/.codex/skills`,
|
|
110
|
+
opencode её НЕ видит) доказывает, что модель не поддакивает, а различает каталоги.
|
|
111
|
+
|
|
112
|
+
- **Antigravity: `~/.agents/skills` → `~/.gemini/config/skills`.** Это
|
|
113
|
+
ЕДИНСТВЕННОЕ место, откуда agy читает навыки (проба BRAVO). Не читает:
|
|
114
|
+
`~/.agents/skills` (ALPHA), `~/.gemini/skills` (CHARLIE), рабочие
|
|
115
|
+
`.agents/skills` и `.gemini/skills` (DELTA/GOLF — даже из настоящего
|
|
116
|
+
git-проекта). Прежний `.agents` — каталог Codex/OpenCode: установщик рапортовал
|
|
117
|
+
успех, а agy навыка не видел (тот же класс ошибки, что снятый `~/.antigravity`;
|
|
118
|
+
третья и последняя итерация этого пути).
|
|
119
|
+
- **Antigravity: project-scope больше НЕ поддержан** — `base_dir(project=...)` /
|
|
120
|
+
`slug_dir(project=...)` / `install(project=...)` бросают новый
|
|
121
|
+
`ScopeUnsupported` (потомок `SkillkitError`/`RuntimeError`). Ставить в каталог,
|
|
122
|
+
который агент не читает, и рапортовать успех — хуже явного отказа. Признак для
|
|
123
|
+
потребителя: `IAgentTarget.supports_project_scope`.
|
|
124
|
+
- **Antigravity: `exists()` — по `~/.gemini/config`**, а не по голому `~/.gemini`
|
|
125
|
+
(последний есть и у gemini-cli). В chain детекта agy стоит последним.
|
|
126
|
+
- **Codex: навыки `~/.codex/skills` → `~/.agents/skills`** (project —
|
|
127
|
+
`<project>/.agents/skills`) по ДОКУМЕНТАЦИИ агента; живьём не проверено —
|
|
128
|
+
аккаунт отдаёт 402 `deactivated_workspace`, помечено как документация, а не
|
|
129
|
+
опыт. Конфиг остался своим: `~/.codex/config.toml` (у таргета теперь два
|
|
130
|
+
корня — `root` для навыков, `config_root` для конфига; оба подставляются в
|
|
131
|
+
тестах). `exists()` — по `~/.codex`, потому что `.agents` общий с OpenCode.
|
|
132
|
+
**Прежний канон не осиротел:** `~/.codex/skills` объявлен
|
|
133
|
+
`CodexTarget.legacy_dirnames` — навык, поставленный китом ДО переезда, снова
|
|
134
|
+
находится (`status`), снимается (`remove`) и переезжает на новый канон
|
|
135
|
+
(`migrate_scope`: наше переносит, чужое пропускает). Заявить старый путь стало
|
|
136
|
+
можно только вместе с гейтом принадлежности (ниже): рядом в `~/.codex/skills`
|
|
137
|
+
лежат десятки чужих каталогов.
|
|
138
|
+
|
|
139
|
+
### Fixed (BREAKING в поведении `remove`) — кит удаляет ТОЛЬКО то, что ставил сам
|
|
140
|
+
|
|
141
|
+
- **`SkillStore.remove` больше не сносит каталог без нашей меты.** Раньше
|
|
142
|
+
найденная copy-папка удалялась `_force_rmtree`'ом безусловно, а после переезда
|
|
143
|
+
Codex каноном стал ОБЩИЙ `~/.agents/skills`, где рядом лежат руками положенные
|
|
144
|
+
чужие навыки (`find-skills`, `notebooklm`, `shadcn` на машине владельца — slug
|
|
145
|
+
`notebooklm` совпадает с нашим уже сейчас). Достаточно было
|
|
146
|
+
`remove(slug="notebooklm")`, чтобы потерять чужие данные; воспроизведено в
|
|
147
|
+
песочнице до правки. Теперь единый гейт принадлежности стоит на ВСЕ таргеты и
|
|
148
|
+
на оба вида установки: путь снимается, только если в нём лежит наша
|
|
149
|
+
`_skill_meta.json` (`skillkit.is_managed_dir`) ЛИБО это ссылка в наш стор
|
|
150
|
+
(отдельная улика — битая ссылка меты не отдаёт, но убрать её надо).
|
|
151
|
+
- **Отказ явный, а не молчаливый:** `RemoveResult.skipped_foreign` (пути, к
|
|
152
|
+
которым не притронулись) + `RemoveResult.message` («каталог не наш … не
|
|
153
|
+
трогаю») для печати потребителем.
|
|
154
|
+
- **`remove(..., force=True)`** — единственный и осознанный выход из-под защиты,
|
|
155
|
+
чтобы правило не создало НЕУДАЛЯЕМЫХ навыков: установки очень старых версий
|
|
156
|
+
кита (и наши копии, из которых мету стёрли руками) меты не имеют. По умолчанию
|
|
157
|
+
выключен: цена ошибки «не удалили» — мусор, цена ошибки «удалили» — чужие данные.
|
|
158
|
+
- **`uv.lock` синхронизирован с `pyproject.toml`** (0.2.4 → 0.3.0). Лок отстал от
|
|
159
|
+
бампа версии, и первый же прогон тестов чинил его сам, делая дерево грязным.
|
|
160
|
+
Держится тестом `tests/test_packaging_lock.py`.
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- **`OpenCodeTarget`** (`skillkit.targets.opencode`, экспортирован из `skillkit`,
|
|
165
|
+
в chain детекта между Codex и Antigravity). Канон: global
|
|
166
|
+
`~/.config/opencode/skills` (JULIET), project `<project>/.opencode/skills`
|
|
167
|
+
(MIKE). Агент читает ещё `~/.opencode/skills` (KILO), `~/.agents/skills`
|
|
168
|
+
(HOTEL) и `<project>/.agents/skills` (LIMA) — кит туда НЕ пишет и НЕ убирает:
|
|
169
|
+
legacy — это «куда кит РАНЬШЕ СТАВИЛ», а не «что агент умеет читать», а в эти
|
|
170
|
+
каталоги навыки OpenCode кит не размещал никогда; `.agents` вдобавок общий с
|
|
171
|
+
Codex, и его владелец — `CodexTarget`. MCP-формат OpenCode не снят ⇒
|
|
172
|
+
`mcp_config()` = `None` (`status=manual`), выдумывать не стали.
|
|
173
|
+
- **`AntigravityTarget.mcp_config()` реализован**: `~/.gemini/config/mcp_config.json`,
|
|
174
|
+
JSON, ключ `mcpServers` (файл найден на диске 2026-07-21). Раньше возвращал
|
|
175
|
+
`None` — «формат неизвестен». Форма совпала с Claude Code, `mcp_register`
|
|
176
|
+
пишет её без правок.
|
|
177
|
+
- **`ScopeUnsupported`** в иерархии ошибок + `BaseAgentTarget.supports_project_scope`
|
|
178
|
+
и `skill_entrypoint` (`"SKILL.md"`; плоский `<имя>.md` не читает ни один агент —
|
|
179
|
+
проба FOXTROT лежала в том же каталоге, что видимая BRAVO, и отличалась ТОЛЬКО
|
|
180
|
+
формой, контролируемый опыт с одной переменной).
|
|
181
|
+
- Тесты `tests/test_agent_skill_layouts_2026_07_21.py` — по одному на каждый путь
|
|
182
|
+
доказанной таблицы (включая «чужое в общих каталогах переживает remove» и
|
|
183
|
+
«форма — каталог, а не плоский .md»). Домашний каталог владельца не задевается:
|
|
184
|
+
во все таргеты подставляется `root=` внутри `tmp_path`.
|
|
185
|
+
- Тесты `tests/test_remove_ownership_guard_2026_07_21.py` — воспроизведение
|
|
186
|
+
сценария потери чужого каталога (для КАЖДОГО таргета), `--keep-local` под тем
|
|
187
|
+
же гейтом, `force` как выход, чужая ссылка не снимается, битая ссылка в наш
|
|
188
|
+
стор снимается, и весь путь миграции Codex со старого канона.
|
|
189
|
+
- `tests/test_packaging_lock.py` — `uv.lock` не расходится с `pyproject.toml`.
|
|
190
|
+
|
|
191
|
+
### Примечание для потребителей
|
|
192
|
+
|
|
193
|
+
Чтобы agy читал навыки вообще, в `~/.gemini/settings.json` нужен
|
|
194
|
+
`experimental.skills = true`. Кит этот файл НЕ трогает — он общий с gemini-cli,
|
|
195
|
+
включение остаётся разовым осознанным действием владельца.
|
|
196
|
+
|
|
197
|
+
## [0.2.4] — 2026-07-21
|
|
198
|
+
|
|
199
|
+
### Added — навык объявляет ХУК, установщик его ставит и снимает
|
|
200
|
+
|
|
201
|
+
- **`[[hooks]]` в `_skill_meta.toml`** — четвёртый артефакт установки рядом с
|
|
202
|
+
`cli` / `mcp` / `runtime_dependencies`: `event` + `command` (обязательны),
|
|
203
|
+
`matcher` / `timeout` / `status_message` / `marker` (опционально). Форма
|
|
204
|
+
эталонная — та же, что у SessionStart-хука `atlas setup`: в настройки агента
|
|
205
|
+
прописывается ВСТРОЕННАЯ команда CLI (`atlas session-hook`), а не файл-скрипт
|
|
206
|
+
(не зависит от `python`/`python3` в PATH). Нормализация как у `onboarding`:
|
|
207
|
+
поля нет → пусто, мусор молча отброшен, publish/install не падает.
|
|
208
|
+
- **`skillkit.hook_register`** — `register_hook` / `unregister_hook` /
|
|
209
|
+
`list_hooks`. Идемпотентно (повтор установки ничего не переписывает), заменяет
|
|
210
|
+
собственную устаревшую запись, **не трогает чужие хуки и чужие настройки**,
|
|
211
|
+
битый файл настроек не затирает (`status=error`), агент с неизвестным
|
|
212
|
+
расположением → `status=manual`.
|
|
213
|
+
- **`IAgentTarget.hook_config()` + `HookConfigDescriptor`** — расположение файла
|
|
214
|
+
настроек знает сам агент (как `mcp_config`). Claude Code: global →
|
|
215
|
+
`<root>/settings.json`, project → `<project>/.claude/settings.json`. Путь
|
|
216
|
+
берётся от `root` таргета, поэтому подстановка `root=` уводит запись в
|
|
217
|
+
песочницу (боевой `~/.claude/settings.json` в тестах не задевается).
|
|
218
|
+
- **`tooling`**: `apply_tooling_artifacts` ставит хуки последним шагом (после
|
|
219
|
+
CLI — хук обычно и есть вызов установленной команды), `revert_tooling_artifacts`
|
|
220
|
+
снимает их при disable/remove. Осиротевший хук иначе звал бы удалённую команду
|
|
221
|
+
на каждом старте сессии агента.
|
|
222
|
+
- Механика мёржа вынесена за seam `hook_register._hook_engine()`: когда в
|
|
223
|
+
`agentskit` появится generic-движок хуков, переезд = замена тела ЭТОЙ функции.
|
|
224
|
+
|
|
225
|
+
### Removed — старая dot-папка Antigravity убрана совсем
|
|
226
|
+
|
|
227
|
+
- `AntigravityTarget.legacy_dirnames` очищен: прежняя dot-папка агента больше не
|
|
228
|
+
известна киту даже на чтение. Решение владельца: она появилась по нашей ошибке
|
|
229
|
+
и агентом не читалась никогда. **Улика (машина владельца, 2026-07-21): папка НЕ
|
|
230
|
+
пустая** — в ней три папки навыков (`google-flow` / `notebooklm` / `telegram`),
|
|
231
|
+
и это НЕ установки кита (нет `_skill_meta.json`, положены руками). Это и есть
|
|
232
|
+
главный довод против legacy-пути: `SkillStore.remove` сносит найденную
|
|
233
|
+
copy-папку rmtree'ом БЕЗ проверки меты и по совпадению slug'а снёс бы чужое.
|
|
234
|
+
Теперь кит туда не смотрит вовсе; `remove` / `resolve_slug_dir` /
|
|
235
|
+
`migrate_scope` / `exists` работают по канону `.agents/skills` и не ломаются.
|
|
236
|
+
Три оставшиеся папки — ручное решение владельца (кит их больше не видит).
|
|
237
|
+
- Сам механизм `legacy_dirnames` в `BaseAgentTarget` сохранён (generic, пригодится
|
|
238
|
+
агенту, который РЕАЛЬНО сменит раскладку); у всех встроенных таргетов он пуст.
|
|
239
|
+
|
|
240
|
+
## [0.2.3] — 2026-07-21
|
|
241
|
+
|
|
242
|
+
### Fixed — Antigravity: навыки ставились туда, куда агент НЕ смотрит
|
|
243
|
+
|
|
244
|
+
- **`AntigravityTarget.dirname`: `.antigravity` → `.agents`.** Канон раскладки
|
|
245
|
+
теперь `<project>/.agents/skills/<name>/SKILL.md` и `~/.agents/skills/…` —
|
|
246
|
+
ровно то, что сам Antigravity называет местом навыков (его вывод «Create new
|
|
247
|
+
skills»: `~/.agents/skills`, `~/.gemini/antigravity-cli/skills`,
|
|
248
|
+
`~/.gemini/skills`). Прежний `~/.antigravity/skills` не читается агентом
|
|
249
|
+
вообще: всё «установленное» лежало мёртвым грузом.
|
|
250
|
+
- **Обратная совместимость на ЧТЕНИЕ (`legacy_dirnames`).** Новое поле
|
|
251
|
+
`BaseAgentTarget.legacy_dirnames` + `legacy_base_dirs()` / `legacy_slug_dirs()`
|
|
252
|
+
/ `resolve_slug_dir()`: пишем в канон, а видим и старую раскладку. Иначе
|
|
253
|
+
status врал бы «навыка нет», а `remove` оставлял мусор.
|
|
254
|
+
- **`SkillStore.remove()`** снимает навык и по канону, и по старым путям;
|
|
255
|
+
`target_dir` в отчёте — то место, где навык реально лежал.
|
|
256
|
+
- **`SkillStore.migrate_scope()`** обходит и старые каталоги: наша copy-установка
|
|
257
|
+
переезжает в стор и линкуется по канону, ссылка по старому пути
|
|
258
|
+
пересоздаётся; чужие (без `_skill_meta.json`) папки не трогаются.
|
|
259
|
+
- `exists()` считает агента установленным и по старой папке (пока пользователь
|
|
260
|
+
не мигрировал, она — единственная улика присутствия агента).
|
|
261
|
+
- Другие таргеты (`claude_code`, `codex`) не задеты: у них `legacy_dirnames` пуст.
|
|
262
|
+
|
|
263
|
+
## [0.2.2] — 2026-07-21
|
|
264
|
+
|
|
265
|
+
### Fixed — монорепо-навык (навык в подпапке репозитория)
|
|
266
|
+
|
|
267
|
+
- **`skill_path` переживает установку**: `_skill_meta.json` теперь хранит
|
|
268
|
+
подпапку навыка, а `SkillStore.update()` берёт её оттуда, если не передали
|
|
269
|
+
явно. Раньше в `update` стоял жёсткий `skill_path=None` — обновление
|
|
270
|
+
материализовало КОРЕНЬ монорепо, и в папку навыка налипали `src/`,
|
|
271
|
+
`pyproject.toml` и копия самого навыка, а `SKILL.md` оказывался этажом ниже
|
|
272
|
+
(в таком виде Claude Code навык не видит).
|
|
273
|
+
- **`preserved_paths` читаются из `_skill_meta.toml`** навыка, а не только из
|
|
274
|
+
захардкоженного списка. Навык может защитить свои данные (папку с БД,
|
|
275
|
+
кэш) — они переживут обновление; общие дефолты (`_local/`,
|
|
276
|
+
`browser_profiles/`) при этом сохраняются.
|
|
277
|
+
|
|
278
|
+
### Added — детект пакетного менеджера (`pkg_manager`)
|
|
279
|
+
|
|
280
|
+
- `detect_project()` определяет экосистему по маркерам: python (`uv.lock`,
|
|
281
|
+
`pyproject.toml`, `requirements.txt`), node с КОНКРЕТНЫМ менеджером по
|
|
282
|
+
lock-файлу (`bun`/`pnpm`/`yarn`/`npm`, при нескольких — по времени
|
|
283
|
+
изменения), rust, go, ruby, php.
|
|
284
|
+
- `missing_manager_report()` вместо голого «не найдены ни uv, ни pip» отдаёт
|
|
285
|
+
машиночитаемые факты: тип проекта и маркер, чего не хватает, команда
|
|
286
|
+
установки под ТЕКУЩУЮ ОС и список реально доступных альтернатив. ИИ-агент
|
|
287
|
+
может довести установку сам.
|
|
288
|
+
|
|
289
|
+
## [0.2.0] — 2026-07-15
|
|
290
|
+
|
|
291
|
+
### Added — install-kit (установка навыка «под ключ» со ВСЕМИ зависимостями)
|
|
292
|
+
|
|
293
|
+
- **`uv_bootstrap.ensure_uv()`** — гарантирует наличие `uv`: `which` →
|
|
294
|
+
официальный Astral-инсталлер (Windows PowerShell `install.ps1` / POSIX
|
|
295
|
+
`curl … install.sh | sh`) → фолбэк `python -m pip install uv`; PATH-плюмбинг
|
|
296
|
+
через `path_store`. Идемпотентно, graceful (не смогли поставить → `None`),
|
|
297
|
+
мокабельно (сеть не дёргается в тестах).
|
|
298
|
+
- **`binary_installer`** — обобщение bespoke-`install_xray` в переиспользуемый
|
|
299
|
+
примитив внешних бинарей с GitHub Releases:
|
|
300
|
+
- `ensure_binary(name, *, github_repo, asset_match, dest_dir, version=None,
|
|
301
|
+
bin_subpath=None, force=False) -> Path` — latest/тег → выбор ассета
|
|
302
|
+
(callable/паттерн) → скачивание → распаковка zip/tar → `chmod 0o755` (POSIX)
|
|
303
|
+
→ удаление архива; идемпотентно; на сбое — `BinaryInstallError`;
|
|
304
|
+
- `platform_asset_name(templates, *, system=None, arch=None)` — резолв имени
|
|
305
|
+
ассета по текущей ОС×архитектуре (ключи `(os, arch)` или строки `"os-arch"`);
|
|
306
|
+
- `is_binary_installed(name, dest_dir, *, bin_subpath=None)`.
|
|
307
|
+
- **`deps_installer`** — новые kind `binary` / `github-release` в
|
|
308
|
+
`runtime_dependencies`: несут spec-словарь `{name, github_repo,
|
|
309
|
+
asset_match|asset_templates, dest, version?, bin_subpath?}` и делегируют
|
|
310
|
+
`ensure_binary`. Ветки `pip`/`npm`/`system` не изменены.
|
|
311
|
+
- **`manifest.build_manifest`** — сохраняет полный spec-словарь бинарей (не
|
|
312
|
+
только `kind`/`spec`), чтобы install мог поставить внешний бинарь.
|
|
313
|
+
- **`bootstrap.bootstrap_skill(skill_dir, *, manifest=None, gate_check=None,
|
|
314
|
+
run_self_check=True) -> dict`** — оркестрация: `ensure_uv` → python-deps
|
|
315
|
+
(`uv sync` при наличии `pyproject.toml`, иначе per-spec) → внешние бинари →
|
|
316
|
+
опциональный `gate_check`-хук (graceful, без жёсткой завязки на s-xray-gate) →
|
|
317
|
+
`run_self_check`. Отчёт `{uv, deps, binaries, gate, self_check}`; ни один шаг
|
|
318
|
+
не роняет установку.
|
|
319
|
+
- **`bootstrap.run_self_check(skill_dir) -> (bool, str)`** — находит и запускает
|
|
320
|
+
`scripts/self_check.py` (venv-python навыка → `uv run` → текущий интерпретатор),
|
|
321
|
+
возвращает `(ok, output)`.
|
|
322
|
+
- Экспорт в `skillkit.__all__`: `ensure_uv`, `ensure_binary`,
|
|
323
|
+
`is_binary_installed`, `platform_asset_name`, `bootstrap_skill`,
|
|
324
|
+
`run_self_check` (+ `BinaryInstallError`, подмодули `uv_bootstrap` /
|
|
325
|
+
`binary_installer` / `bootstrap`).
|
|
326
|
+
|
|
327
|
+
### Changed
|
|
328
|
+
|
|
329
|
+
- `version` 0.1.9 → 0.2.0.
|