s-skillkit 0.3.6__tar.gz → 0.4.0__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.3.6 → s_skillkit-0.4.0}/CHANGELOG.md +52 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/PKG-INFO +2 -2
- s_skillkit-0.4.0/docs/adr/0000-template.md +20 -0
- s_skillkit-0.4.0/docs/adr/0001-kanon-navyka-zhivet-v-skillkit.md +122 -0
- s_skillkit-0.4.0/docs/adr/README.md +30 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/pyproject.toml +10 -1
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/__init__.py +34 -4
- s_skillkit-0.4.0/skillkit/canon.py +1046 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/config.py +14 -2
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/errors.py +34 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/hook_register.py +1 -1
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/__init__.py +6 -6
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/meta.py +3 -3
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/manifest.py +114 -3
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/mcp_register.py +212 -8
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/path_store.py +114 -6
- s_skillkit-0.4.0/skillkit/plugin_register.py +260 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/__init__.py +2 -2
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/antigravity.py +27 -1
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/base.py +80 -3
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/claude_code.py +28 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/detect.py +2 -2
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/tooling.py +51 -3
- s_skillkit-0.4.0/tests/conftest.py +40 -0
- s_skillkit-0.4.0/tests/test_artifact_ownership_2272.py +411 -0
- s_skillkit-0.4.0/tests/test_canon.py +233 -0
- s_skillkit-0.4.0/tests/test_claude_frontmatter.py +60 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_destructive_ownership_gate_2026_07_21.py +6 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_manifest.py +67 -0
- s_skillkit-0.4.0/tests/test_plugin_register.py +163 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_targets.py +1 -1
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_tooling.py +3 -3
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/uv.lock +1 -1
- s_skillkit-0.3.6/tests/conftest.py +0 -21
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/.gitignore +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/.gitlab-ci.yml +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/AGENTS.md +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/LICENSE +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/README.md +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/binary_installer.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/bootstrap.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/collections.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/deps_installer.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/filter.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/differ.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/fetchers.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/fsops.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/models.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/installer/ownership.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/linker.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/paths.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/pkg_manager.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/proc.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/project.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/codex.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/targets/opencode.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/telemetry.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/skillkit/uv_bootstrap.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_agent_skill_layouts_2026_07_21.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_binary_installer.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_bootstrap.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_collections.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_config.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_deps_binary.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_deps_installer.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_errors.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_filter.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_git_install.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_hooks.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_installer.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_legacy_paths_no_orphans_2026_07_21.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_mcp_register.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_no_raw_subprocess.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_packaging_lock.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_path_store.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_paths.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_pkg_manager.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_pkgsrc_not_in_agent_zone.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_proc.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_project.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_remove_ownership_guard_2026_07_21.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_stub_guard.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_telemetry_adapter.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_transactional_install.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_traversal_guard.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_typed_fields.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_uv_bootstrap.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_w268_subdir_skill.py +0 -0
- {s_skillkit-0.3.6 → s_skillkit-0.4.0}/tests/test_w272_empty_manifest_guard.py +0 -0
|
@@ -4,6 +4,58 @@
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/ru/1.1.0/); версионирование —
|
|
5
5
|
[SemVer](https://semver.org/lang/ru/).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added — канон навыка переехал в кит: `skillkit.canon` (#162)
|
|
10
|
+
|
|
11
|
+
- **Один источник правды вместо трёх.** Форма навыка (раскладка `src/<пакет>/` +
|
|
12
|
+
`skills/<slug>/` + `install/` + `tests/`, frontmatter `SKILL.md`, порядок ключей
|
|
13
|
+
`_skill_meta.toml`, `pyproject` с синхронной версией и командой) жила в трёх
|
|
14
|
+
копиях: `clikit.scaffold`, `skillery_cli.templates` и
|
|
15
|
+
`reverse_factory.factory.canon`. Копии разошлись: скаффолд `clikit` собирал
|
|
16
|
+
плоскую раскладку с `kind = "cli"` — родом, которого нет вовсе, — и такой навык
|
|
17
|
+
не проходил валидатор. Канон перенесён в кит целиком, вместе с тестами.
|
|
18
|
+
- **Почему в `skillkit`.** Всё, что рендерит канон, читает и ставит этот же кит:
|
|
19
|
+
`manifest` разбирает `_skill_meta.toml`, `targets` раскладывает папку навыка по
|
|
20
|
+
агентам, `bootstrap` поднимает зависимости и self-check, а сгенерированный
|
|
21
|
+
`install/install.py` — тонкая обёртка над китом. Обоснование и отклонённые
|
|
22
|
+
варианты — `docs/adr/0001-kanon-navyka-zhivet-v-skillkit.md`.
|
|
23
|
+
- **Что появилось:** `SkillLayout` / `layout`, `SkillMeta`, `render_skill_md`,
|
|
24
|
+
`render_skill_meta`, `render_pyproject`, `render_install_docs`,
|
|
25
|
+
`render_skillgateignore`, `validate_canonical_skill`, `ALLOWED_KINDS`,
|
|
26
|
+
`module_name` / `command_name`, `read_declared_name` / `stamp_declared_name`.
|
|
27
|
+
- **Форма против смысла.** Канон держит форму; тело `SKILL.md` и описание
|
|
28
|
+
приносит производитель через `body=` / `description=`. Тело по умолчанию —
|
|
29
|
+
нейтральный каркас AK-канона (роутер + пять секций с TODO), без упоминаний
|
|
30
|
+
конкретной фабрики: кит остаётся брендо-нейтральным.
|
|
31
|
+
- **Зависимостей не прибавилось:** модуль на голом stdlib (`re`, `tomllib`,
|
|
32
|
+
`dataclasses`, `pathlib`).
|
|
33
|
+
|
|
34
|
+
### Fixed — гейт принадлежности у CLI-команд и MCP-серверов (#2272)
|
|
35
|
+
|
|
36
|
+
- **Навык больше не забирает чужую команду.** `path_store.add_cli` снимал ЛЮБОЙ
|
|
37
|
+
существующий shim и переписывал sidecar, не сверяя владельца: второй навык с
|
|
38
|
+
тем же `command_name` молча забирал команду первого. Теперь владелец читается
|
|
39
|
+
из sidecar'а (`cli_owner` / `cli_ownership`), и попытка занять чужое —
|
|
40
|
+
`ArtifactConflict`, а не перезапись.
|
|
41
|
+
- **Снятие навыка не уносит чужое.** `remove_cli(command_name, skill_slug=…)`
|
|
42
|
+
удаляет запись только при совпадении владельца. Без `skill_slug`
|
|
43
|
+
(административное снятие «по имени») удаляется лишь то, у чего есть НАШ
|
|
44
|
+
sidecar: файл, которого кит не клал, не трогается никогда.
|
|
45
|
+
- **MCP-сервер, настроенный пользователем РУКАМИ, защищён.** `register_mcp` /
|
|
46
|
+
`unregister_mcp` принимают `skill_slug` и сверяются с реестром владения
|
|
47
|
+
`<config_dir>/mcp_owners.json` (маркер держим СНАРУЖИ чужого конфига агента —
|
|
48
|
+
дописывать служебные ключи в запись сервера значит рисковать её валидностью).
|
|
49
|
+
Чужая или безмаркерная запись → `status=conflict`, конфиг не меняется.
|
|
50
|
+
- **Отчёты.** `apply_tooling_artifacts` отдаёт `status=conflict` (с `owner`) по
|
|
51
|
+
затронутому артефакту, `revert_tooling_artifacts` — список `conflicts`.
|
|
52
|
+
Установка/снятие самого навыка при этом не ломаются.
|
|
53
|
+
- **Совместимость.** Sidecar старого формата (без `skill_slug`) считается нашим
|
|
54
|
+
и получает владельца при первой перезаписи; MCP-запись, сделанная до
|
|
55
|
+
появления реестра, «усыновляется», если её содержимое совпадает с тем, что
|
|
56
|
+
положил бы этот же навык сейчас. Повторная установка ТОГО ЖЕ навыка (в т.ч. с
|
|
57
|
+
новым entrypoint/config) проходит как раньше.
|
|
58
|
+
|
|
7
59
|
## [0.3.6]
|
|
8
60
|
|
|
9
61
|
### Changed — своя политика запуска подпроцессов, s-librarykit убран (#1644)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: s-skillkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Локальное ядро управления навыками: install из git/локальной папки, junction/symlink-линковка в scope агента, .skillignore-фильтр, build manifest, project-манифест. 0 завязок на сеть/auth — stdlib + tomli-w + pathspec + platformdirs.
|
|
5
5
|
Author: Dmitry
|
|
6
6
|
License: MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# NNNN. Краткий заголовок решения
|
|
2
|
+
|
|
3
|
+
- **Статус:** proposed | accepted | superseded (→ ADR-NNNN) | deprecated
|
|
4
|
+
- **Дата:** ГГГГ-ММ-ДД
|
|
5
|
+
|
|
6
|
+
## Контекст
|
|
7
|
+
|
|
8
|
+
Какую ситуацию/проблему решаем. Что вынуждает принять решение сейчас: ограничения, требования, силы, которые тянут в разные стороны. Достаточно, чтобы понять «почему» без внешнего контекста.
|
|
9
|
+
|
|
10
|
+
## Решение
|
|
11
|
+
|
|
12
|
+
Что именно решили — одной-двумя фразами, в настоящем времени: «Делаем X». Конкретно и проверяемо.
|
|
13
|
+
|
|
14
|
+
## Последствия
|
|
15
|
+
|
|
16
|
+
Что становится проще и что — сложнее/дороже после принятия. Плюсы, минусы, новые обязательства и ограничения, которые теперь придётся держать.
|
|
17
|
+
|
|
18
|
+
## Альтернативы
|
|
19
|
+
|
|
20
|
+
Какие варианты рассматривали и **почему отклонили** (в этом ценность записи). Кратко: вариант → причина отказа / отложено до условия.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 0001. Канон навыка живёт в skillkit — рядом с тем, кто его читает
|
|
2
|
+
|
|
3
|
+
- **Статус:** accepted
|
|
4
|
+
- **Дата:** 2026-09-01
|
|
5
|
+
|
|
6
|
+
## Контекст
|
|
7
|
+
|
|
8
|
+
«Навык в каноне Skillery» — это набор файлов строго определённой формы:
|
|
9
|
+
`skills/<slug>/SKILL.md` с frontmatter, `skills/<slug>/_skill_meta.toml`,
|
|
10
|
+
`src/<пакет>/` (src-layout), `tests/`, `install/`, `.skillgateignore`,
|
|
11
|
+
`pyproject.toml` с той же версией и той же консольной командой, что в манифесте.
|
|
12
|
+
|
|
13
|
+
Эту форму на диск кладут РАЗНЫЕ производители, и каждый завёл свою копию правил:
|
|
14
|
+
|
|
15
|
+
1. **`clikit.scaffold`** (790 строк) — свои `render_skill_md` / `render_skill_meta` /
|
|
16
|
+
`render_pyproject` / `render_install_skill` / `render_self_check` /
|
|
17
|
+
`render_smoke_test`. Раскладка ДРУГАЯ: плоский `<pkg>/` вместо `src/<pkg>/`,
|
|
18
|
+
`SKILL.md` и `_skill_meta.toml` в КОРНЕ вместо `skills/<slug>/`,
|
|
19
|
+
`kind = "cli"` — рода, которого нет в списке допустимых
|
|
20
|
+
(`prompt` / `comprehensive` / `tooling`), — ни `install/`, ни `tests/`.
|
|
21
|
+
2. **`skillery_cli.templates`** (802 строки) — свои `render_skill_meta_toml`,
|
|
22
|
+
онбординг-триада `scripts/{install_skill,self_check,smoke_test}.py`,
|
|
23
|
+
`render_pyproject`, CLI-скелет. В докстроке прямо написано «зеркалит
|
|
24
|
+
`clikit.scaffold.render_cli_module`» — то есть копия по замыслу.
|
|
25
|
+
3. **`reverse_factory.factory.canon`** (1033 строки) — раскладка, рендер,
|
|
26
|
+
`validate_canonical_skill`; три производителя фабрики уже сведены на него
|
|
27
|
+
фитнес-функцией `test_factory_canon_single_source.py`.
|
|
28
|
+
|
|
29
|
+
Цена расхождения не гипотетическая: навык, собранный `clikit.scaffold_cli`, не
|
|
30
|
+
проходит `validate_canonical_skill` — его топологию перед публикацией переводили
|
|
31
|
+
РУКАМИ. Ровно этот ручной шаг канон и должен был убрать.
|
|
32
|
+
|
|
33
|
+
Выбирать источник пришлось сейчас потому, что канон фабрики только что обкатан
|
|
34
|
+
живьём (генерация → `uv sync` → `uv run pytest` → боевой прогон установщика,
|
|
35
|
+
reverse-factory 84c7749), и держать рядом с работающим ещё две копии — значит
|
|
36
|
+
гарантированно разойтись с ним снова.
|
|
37
|
+
|
|
38
|
+
## Решение
|
|
39
|
+
|
|
40
|
+
**Единственный канон навыка — модуль `skillkit.canon`.** Туда переносится
|
|
41
|
+
проверенный канон фабрики целиком: раскладка (`SkillLayout`, `layout`), рендер
|
|
42
|
+
(`render_skill_md` / `render_skill_meta` / `render_pyproject` /
|
|
43
|
+
`render_install_docs` / `render_skillgateignore`) и валидатор
|
|
44
|
+
`validate_canonical_skill` вместе с его тестами.
|
|
45
|
+
|
|
46
|
+
Почему именно `skillkit`, а не `clikit` и не фабрика:
|
|
47
|
+
|
|
48
|
+
- **Писатель формата обязан жить рядом с читателем.** Всё, что рендерит канон,
|
|
49
|
+
читает и ставит этот же кит: `_skill_meta.toml` разбирает `skillkit.manifest`,
|
|
50
|
+
папку навыка раскладывает по агентам `skillkit.targets`, зависимости и
|
|
51
|
+
self-check поднимает `skillkit.bootstrap`. Сгенерированный `install/install.py`
|
|
52
|
+
— буквально тонкая обёртка над `skillkit`. Разведи их по разным репозиториям —
|
|
53
|
+
и они разойдутся молча, как уже разошлись.
|
|
54
|
+
- **`clikit` — не та зона.** Кит отвечает за плоть CLI (command-kit, вывод,
|
|
55
|
+
конфиг, транспорт) и про навыки не знает ничего. То, что он рендерил
|
|
56
|
+
`_skill_meta.toml` с несуществующим `kind = "cli"`, — прямое следствие того,
|
|
57
|
+
что формат жил не у своего владельца.
|
|
58
|
+
- **Фабрика — потребитель, а не библиотека.** `reverse-factory` тянет playwright,
|
|
59
|
+
camoufox, nodriver. Сделать её зависимостью клиента `skillery` нельзя.
|
|
60
|
+
- **Кит уже общий.** И `reverse-factory`, и `skillery-cli` уже объявляют
|
|
61
|
+
`s-skillkit>=0.3.7` — новая зависимость никому не заводится.
|
|
62
|
+
- **Канон ничего не тянет.** Модуль на голом stdlib (`re`, `tomllib`,
|
|
63
|
+
`dataclasses`, `pathlib`): TOML пишем сами, читаем через `tomllib`. Лёгкий кит
|
|
64
|
+
остаётся лёгким.
|
|
65
|
+
|
|
66
|
+
Граница «форма против смысла»: канон держит ФОРМУ (frontmatter, аллоулист
|
|
67
|
+
`files`, порядок ключей манифеста, раскладку файлов, состав `install/`), а СМЫСЛ
|
|
68
|
+
приносит производитель через `body=` / `description=`. Поэтому тело `SKILL.md` по
|
|
69
|
+
умолчанию в ките нейтральное (каркас AK-канона с TODO), а реверс-специфичное тело
|
|
70
|
+
остаётся в фабрике.
|
|
71
|
+
|
|
72
|
+
Остальные места зовут канон, а не копируют его:
|
|
73
|
+
|
|
74
|
+
- `reverse_factory.factory.canon` — тонкий ре-экспорт `skillkit.canon` плюс своё
|
|
75
|
+
реверс-тело `SKILL.md`. Путь модуля сохранён: по нему ходят шесть импортёров и
|
|
76
|
+
фитнес-функция.
|
|
77
|
+
- `clikit.scaffold` — рендер навыка удалён целиком; кит скаффолдит только CLI.
|
|
78
|
+
|
|
79
|
+
## Последствия
|
|
80
|
+
|
|
81
|
+
Правило канона теперь ровно одно на всю экосистему: правка формы навыка делается
|
|
82
|
+
в `skillkit.canon`, и её видят все производители сразу. Валидатор и рендер лежат
|
|
83
|
+
рядом, поэтому «рендер выдал то, что валидатор не принимает» ловится тестом в
|
|
84
|
+
самом ките.
|
|
85
|
+
|
|
86
|
+
`clikit` перестал уметь генерировать навык — это НАМЕРЕННАЯ потеря. Кто
|
|
87
|
+
скаффолдил навык через `clikit.scaffold_cli(skill=True)`, получал невалидный
|
|
88
|
+
навык; теперь он получает CLI-пакет и берёт навык у `skillkit.canon` (или у
|
|
89
|
+
`skillery new`). Обратная совместимость сломана осознанно: сохранять способ
|
|
90
|
+
сделать заведомо непроходящий навык — хуже, чем убрать его.
|
|
91
|
+
|
|
92
|
+
Кит подрос: `skillkit` перестал быть чисто ФС-механикой и взял на себя ещё и
|
|
93
|
+
рендер. Оправдание — тот же аргумент, что и в решении: формат, который кит
|
|
94
|
+
читает, он же должен и писать. Брендо-нейтральность при этом сохранена: имён
|
|
95
|
+
конкретных продуктов и фабрик в модуле нет.
|
|
96
|
+
|
|
97
|
+
Обязательство на будущее: `skillery-cli` пока держит свою копию (`templates/`).
|
|
98
|
+
Пока миграция не закончена, у канона фактически два писателя, и расхождение
|
|
99
|
+
возможно — это записано отдельной задачей и закрывается фитнес-функцией на
|
|
100
|
+
стороне клиента, а не договорённостью.
|
|
101
|
+
|
|
102
|
+
## Альтернативы
|
|
103
|
+
|
|
104
|
+
**Оставить канон в `reverse-factory`, остальным импортировать оттуда.** Отклонено:
|
|
105
|
+
фабрика тянет браузерный стек (playwright / camoufox / nodriver), и клиент
|
|
106
|
+
`skillery`, который ставится пользователю одной командой, не может от неё
|
|
107
|
+
зависеть. Библиотечный код в приложении — инверсия слоёв.
|
|
108
|
+
|
|
109
|
+
**Сделать каноном `clikit`.** Отклонено: чужая зона. `clikit` — плоть CLI, про
|
|
110
|
+
навыки не знает; именно его версия канона и разошлась сильнее всех
|
|
111
|
+
(`kind = "cli"`, плоская раскладка). Кроме того, `clikit` не зависит от
|
|
112
|
+
`skillkit`, а канон обязан согласовываться с читателем манифеста — пришлось бы
|
|
113
|
+
либо заводить зависимость снизу вверх, либо снова дублировать знание.
|
|
114
|
+
|
|
115
|
+
**Отдельный кит `skillcanonkit`.** Отклонено как лишняя сущность: канон — 1000
|
|
116
|
+
строк stdlib, целиком про навык, и уже сейчас неотделим от `manifest`/`targets`.
|
|
117
|
+
Новый кит дал бы четвёртый репозиторий, четвёртый выпуск и четвёртую версию в
|
|
118
|
+
чужих зависимостях ради модуля, у которого один естественный владелец.
|
|
119
|
+
|
|
120
|
+
**Не переносить, а описать канон документом и проверять линтером.** Отклонено:
|
|
121
|
+
документ не рендерит. Копии остались бы копиями, а расхождение ловилось бы
|
|
122
|
+
постфактум — ровно так и жили до сих пор.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ADR — журнал архитектурных решений
|
|
2
|
+
|
|
3
|
+
**Что это.** ADR (Architecture Decision Record) — короткий документ на **одно** архитектурное решение: контекст → что решили → почему → последствия. Лежат здесь, в `docs/adr/`, со **сквозной нумерацией** (`0001-…`, `0002-…`). Шаблон — [`0000-template.md`](0000-template.md).
|
|
4
|
+
|
|
5
|
+
**Зачем.** «Почему» проекта нельзя держать в комментариях к задачам и переписке — оно теряется. ADR — дистиллят решения: агент читает его **перед** правкой и не «чинит» то, что было выбрано сознательно. Дёшев по токенам: одно решение прочитать проще, чем реконструировать его из истории коммитов.
|
|
6
|
+
|
|
7
|
+
## Когда заводить ADR — когда сошлись 3 условия
|
|
8
|
+
|
|
9
|
+
1. решение **дорого откатить** (влияет на структуру, данные, контракты);
|
|
10
|
+
2. без контекста оно **выглядит странно** (кто-то потом захочет «починить»);
|
|
11
|
+
3. были **реальные альтернативы** и выбор — осознанный трейдофф.
|
|
12
|
+
|
|
13
|
+
Если хотя бы одно условие не выполнено — ADR не нужен, хватит `comment` на задаче в Atlas.
|
|
14
|
+
|
|
15
|
+
## Правила ведения
|
|
16
|
+
|
|
17
|
+
- **Нумерация сквозная**, файл — `NNNN-краткий-слаг.md` (`0001-operating-model-foundamentals.md`).
|
|
18
|
+
- **Не редактировать задним числом.** Принятое решение — исторический факт. Если решение отменено новым — старому ставим `Статус: superseded` (со ссылкой на новое ADR), а не переписываем его.
|
|
19
|
+
- **«Схлопывание».** Когда цепочка `superseded` разрослась, допустимо свести её к одному ADR с текущим состоянием — ради экономии контекста агента. Прежние помечаем как схлопнутые.
|
|
20
|
+
- **Статусы:** `proposed` → `accepted` → (`superseded` / `deprecated`).
|
|
21
|
+
|
|
22
|
+
## Что НЕ идёт в ADR
|
|
23
|
+
|
|
24
|
+
- **состояние** (ветка/worktree, стадия) — это тег/поле задачи в Atlas;
|
|
25
|
+
- **ход работы / handoff** — `comment` на задаче в Atlas;
|
|
26
|
+
- **черновой план реализации** — scratchpad, в `.gitignore`, в репо не коммитится.
|
|
27
|
+
|
|
28
|
+
Полная модель фиксации артефактов (4 слоя) — в `ai-ecosystem-core/AGENT-OPERATING-MODEL.md`, раздел «Модель фиксации артефактов».
|
|
29
|
+
|
|
30
|
+
**Методология:** практика ADR (Michael Nygard), компактный формат Matt Pocock (`github.com/mattpocock/skills`, grill-with-docs), примеры в NATS / AG2.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "s-skillkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Локальное ядро управления навыками: install из git/локальной папки, junction/symlink-линковка в scope агента, .skillignore-фильтр, build manifest, project-манифест. 0 завязок на сеть/auth — stdlib + tomli-w + pathspec + platformdirs."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -55,6 +55,15 @@ select = ["E", "F", "I", "B", "UP", "N", "SIM", "RUF"]
|
|
|
55
55
|
# возвращает (store, target, store_dir); читаемее, чем индексирование).
|
|
56
56
|
ignore = ["RUF001", "RUF002", "RUF003", "RUF022", "RUF059"]
|
|
57
57
|
|
|
58
|
+
[tool.ruff.lint.per-file-ignores]
|
|
59
|
+
# canon.py несёт ДОСЛОВНЫЕ шаблоны генерируемых файлов (install.ps1 / install.sh /
|
|
60
|
+
# install.py / _skill_meta.toml). Перенос строки внутри такого шаблона — это правка
|
|
61
|
+
# ЧУЖОГО файла, который потом ляжет на диск навыка: команда PowerShell,
|
|
62
|
+
# разорванная ради длины, просто не выполнится. Ограничение длины снято для
|
|
63
|
+
# модуля целиком, потому что `# noqa` внутри многострочного литерала уехал бы в
|
|
64
|
+
# сам вывод.
|
|
65
|
+
"skillkit/canon.py" = ["E501"]
|
|
66
|
+
|
|
58
67
|
[tool.pytest.ini_options]
|
|
59
68
|
asyncio_mode = "auto"
|
|
60
69
|
testpaths = ["tests"]
|
|
@@ -10,8 +10,11 @@ platformdirs. Запуск подпроцессов (uv/git/cmd/powershell) —
|
|
|
10
10
|
локальная папка / stub) + junction/symlink-линковка в scope агента,
|
|
11
11
|
инкрементальный update по sha-diff, remove/keep-local/purge, migrate_scope.
|
|
12
12
|
Включает P0 stub-would-clobber guard.
|
|
13
|
-
- ``targets`` — ``
|
|
13
|
+
- ``targets`` — ``AgentTarget`` + детект агента (Claude Code / Codex /
|
|
14
14
|
OpenCode / Antigravity). Раскладки сверены с живым опытом 2026-07-21.
|
|
15
|
+
- ``canon`` — КАНОН раскладки навыка: рендер (SKILL.md / _skill_meta.toml /
|
|
16
|
+
pyproject / install/ / .skillgateignore) и ``validate_canonical_skill``.
|
|
17
|
+
Писатель формата живёт рядом с читателем (``manifest``/``targets``) — ADR-0001.
|
|
15
18
|
- ``manifest`` — build manifest для publish + ридеры frontmatter / _skill_meta.toml
|
|
16
19
|
(в т.ч. артефакты установки: cli / mcp / runtime_dependencies / onboarding /
|
|
17
20
|
hooks).
|
|
@@ -44,6 +47,7 @@ from __future__ import annotations
|
|
|
44
47
|
from skillkit import (
|
|
45
48
|
binary_installer,
|
|
46
49
|
bootstrap,
|
|
50
|
+
canon,
|
|
47
51
|
collections,
|
|
48
52
|
deps_installer,
|
|
49
53
|
filter,
|
|
@@ -64,9 +68,22 @@ from skillkit.binary_installer import (
|
|
|
64
68
|
platform_asset_name,
|
|
65
69
|
)
|
|
66
70
|
from skillkit.bootstrap import bootstrap_skill, run_self_check
|
|
71
|
+
from skillkit.canon import (
|
|
72
|
+
ALLOWED_KINDS,
|
|
73
|
+
SkillLayout,
|
|
74
|
+
SkillMeta,
|
|
75
|
+
layout,
|
|
76
|
+
render_install_docs,
|
|
77
|
+
render_pyproject,
|
|
78
|
+
render_skill_md,
|
|
79
|
+
render_skill_meta,
|
|
80
|
+
render_skillgateignore,
|
|
81
|
+
validate_canonical_skill,
|
|
82
|
+
)
|
|
67
83
|
from skillkit.collections import LocalCollectionError
|
|
68
84
|
from skillkit.config import SkillkitConfig, configure, reset_to_defaults
|
|
69
85
|
from skillkit.errors import (
|
|
86
|
+
ArtifactConflict,
|
|
70
87
|
CloneError,
|
|
71
88
|
ForeignPathError,
|
|
72
89
|
MaterializeError,
|
|
@@ -110,7 +127,7 @@ from skillkit.targets import (
|
|
|
110
127
|
ClaudeCodeTarget,
|
|
111
128
|
CodexTarget,
|
|
112
129
|
HookConfigDescriptor,
|
|
113
|
-
|
|
130
|
+
AgentTarget,
|
|
114
131
|
McpConfigDescriptor,
|
|
115
132
|
OpenCodeTarget,
|
|
116
133
|
agent_skill_dir,
|
|
@@ -119,7 +136,7 @@ from skillkit.targets import (
|
|
|
119
136
|
)
|
|
120
137
|
from skillkit.uv_bootstrap import ensure_uv
|
|
121
138
|
|
|
122
|
-
__version__ = "0.
|
|
139
|
+
__version__ = "0.4.0"
|
|
123
140
|
|
|
124
141
|
__all__ = [
|
|
125
142
|
"__version__",
|
|
@@ -152,6 +169,7 @@ __all__ = [
|
|
|
152
169
|
"Source",
|
|
153
170
|
# доменные исключения (иерархия SkillkitError)
|
|
154
171
|
"SkillkitError",
|
|
172
|
+
"ArtifactConflict",
|
|
155
173
|
"CloneError",
|
|
156
174
|
"ScopeConflict",
|
|
157
175
|
"ForeignPathError",
|
|
@@ -166,7 +184,7 @@ __all__ = [
|
|
|
166
184
|
"stamp_ownership",
|
|
167
185
|
"skill_dir_name",
|
|
168
186
|
# targets
|
|
169
|
-
"
|
|
187
|
+
"AgentTarget",
|
|
170
188
|
"BaseAgentTarget",
|
|
171
189
|
"ClaudeCodeTarget",
|
|
172
190
|
"CodexTarget",
|
|
@@ -177,6 +195,17 @@ __all__ = [
|
|
|
177
195
|
"agent_skill_dir",
|
|
178
196
|
"detect_agent",
|
|
179
197
|
"get_target",
|
|
198
|
+
# канон раскладки навыка (рендер + валидатор)
|
|
199
|
+
"ALLOWED_KINDS",
|
|
200
|
+
"SkillLayout",
|
|
201
|
+
"SkillMeta",
|
|
202
|
+
"layout",
|
|
203
|
+
"render_install_docs",
|
|
204
|
+
"render_pyproject",
|
|
205
|
+
"render_skill_md",
|
|
206
|
+
"render_skill_meta",
|
|
207
|
+
"render_skillgateignore",
|
|
208
|
+
"validate_canonical_skill",
|
|
180
209
|
# manifest
|
|
181
210
|
"BuiltManifest",
|
|
182
211
|
"build_manifest",
|
|
@@ -192,6 +221,7 @@ __all__ = [
|
|
|
192
221
|
"bootstrap_skill",
|
|
193
222
|
"run_self_check",
|
|
194
223
|
# подмодули (ФС-механика навыка)
|
|
224
|
+
"canon",
|
|
195
225
|
"linker",
|
|
196
226
|
"filter",
|
|
197
227
|
"manifest",
|