s-skillkit 0.4.0__tar.gz → 0.5.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.5.0/.gitlab-ci.yml +101 -0
- s_skillkit-0.5.0/.importlinter +66 -0
- s_skillkit-0.5.0/.skillgateignore +12 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/CHANGELOG.md +102 -1
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/PKG-INFO +4 -4
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/pyproject.toml +28 -2
- s_skillkit-0.5.0/scripts/check_dist_leaks.py +180 -0
- s_skillkit-0.5.0/scripts/release_guard.py +138 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/__init__.py +4 -2
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/binary_installer.py +3 -3
- s_skillkit-0.5.0/skillkit/installer_cli/__init__.py +77 -0
- s_skillkit-0.5.0/skillkit/installer_cli/_branding.py +30 -0
- s_skillkit-0.5.0/skillkit/installer_cli/_detached.py +33 -0
- s_skillkit-0.5.0/skillkit/installer_cli/config.py +104 -0
- s_skillkit-0.5.0/skillkit/installer_cli/direct.py +285 -0
- s_skillkit-0.5.0/skillkit/installer_cli/doctor.py +88 -0
- s_skillkit-0.5.0/skillkit/installer_cli/install_scripts.py +108 -0
- s_skillkit-0.5.0/skillkit/installer_cli/publish.py +331 -0
- s_skillkit-0.5.0/skillkit/installer_cli/self_update.py +217 -0
- s_skillkit-0.5.0/skillkit/installer_cli/tokens.py +170 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/__init__.py +8 -1
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/uv_bootstrap.py +2 -1
- s_skillkit-0.5.0/tests/conftest.py +68 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_bootstrap.py +1 -4
- s_skillkit-0.5.0/tests/test_installer_cli_direct.py +147 -0
- s_skillkit-0.5.0/tests/test_installer_cli_doctor.py +103 -0
- s_skillkit-0.5.0/tests/test_installer_cli_publish.py +160 -0
- s_skillkit-0.5.0/tests/test_installer_cli_smoke.py +104 -0
- s_skillkit-0.5.0/tests/test_installer_cli_tokens.py +129 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_no_raw_subprocess.py +12 -6
- s_skillkit-0.5.0/tests/test_targets_public_names.py +87 -0
- s_skillkit-0.5.0/uv.lock +1129 -0
- s_skillkit-0.4.0/.gitlab-ci.yml +0 -31
- s_skillkit-0.4.0/tests/conftest.py +0 -40
- s_skillkit-0.4.0/uv.lock +0 -150
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/.gitignore +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/AGENTS.md +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/LICENSE +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/README.md +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/docs/adr/0000-template.md +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/docs/adr/0001-kanon-navyka-zhivet-v-skillkit.md +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/docs/adr/README.md +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/bootstrap.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/canon.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/collections.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/config.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/deps_installer.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/errors.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/filter.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/hook_register.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/__init__.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/differ.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/fetchers.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/fsops.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/meta.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/models.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/installer/ownership.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/linker.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/manifest.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/mcp_register.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/path_store.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/paths.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/pkg_manager.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/plugin_register.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/proc.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/project.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/antigravity.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/base.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/claude_code.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/codex.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/detect.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/targets/opencode.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/telemetry.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/skillkit/tooling.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_agent_skill_layouts_2026_07_21.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_artifact_ownership_2272.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_binary_installer.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_canon.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_claude_frontmatter.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_collections.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_config.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_deps_binary.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_deps_installer.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_destructive_ownership_gate_2026_07_21.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_errors.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_filter.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_git_install.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_hooks.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_installer.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_legacy_paths_no_orphans_2026_07_21.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_manifest.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_mcp_register.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_packaging_lock.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_path_store.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_paths.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_pkg_manager.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_pkgsrc_not_in_agent_zone.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_plugin_register.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_proc.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_project.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_remove_ownership_guard_2026_07_21.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_stub_guard.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_targets.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_telemetry_adapter.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_tooling.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_transactional_install.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_traversal_guard.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_typed_fields.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_uv_bootstrap.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_w268_subdir_skill.py +0 -0
- {s_skillkit-0.4.0 → s_skillkit-0.5.0}/tests/test_w272_empty_manifest_guard.py +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# СГЕНЕРИРОВАНО из devcontour/tools/kits/template/gitlab-ci.yml — не правь здесь.
|
|
2
|
+
# Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
|
|
3
|
+
# Сверка: `python tools/kits/apply_template.py <кит> --check` (код 1 при расхождении).
|
|
4
|
+
#
|
|
5
|
+
# Автопубликация на PyPI по семвер-тегу vX.Y.Z через Trusted Publishing (OIDC).
|
|
6
|
+
# Три стадии, и это не украшение: версии на PyPI неизменяемы, и шесть китов
|
|
7
|
+
# уезжали туда без единого теста (browserkit 0.0.18, netkit ≤0.0.12 — оба в бою).
|
|
8
|
+
# gates — тесты, линтер, слои, doctor: на каждый push и на тег
|
|
9
|
+
# release-guard — тег == версия во всех местах (pyproject/uv.lock/CHANGELOG)
|
|
10
|
+
# publish-pypi — только после двух первых; версия, уже лежащая на PyPI, не
|
|
11
|
+
# публикуется повторно (тег «догоняет» выпуск, а не роняет CI);
|
|
12
|
+
# после сборки и до обмена токена — check_dist_leaks.py
|
|
13
|
+
# (личные данные разработчика в собранном дистрибутиве)
|
|
14
|
+
# Токены НИГДЕ не хранятся: GitLab выдаёт короткоживущий OIDC-токен (PYPI_ID_TOKEN),
|
|
15
|
+
# он одноразово обменивается на PyPI API-токен. Издатель настраивается на pypi.org:
|
|
16
|
+
# Manage -> Publishing -> GitLab (namespace=S-kits, project=skillkit,
|
|
17
|
+
# top-level pipeline file=.gitlab-ci.yml, environment — пусто).
|
|
18
|
+
# Дистрибутив на PyPI: s-skillkit.
|
|
19
|
+
stages: [test, guard, publish]
|
|
20
|
+
|
|
21
|
+
.uv:
|
|
22
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm
|
|
23
|
+
|
|
24
|
+
gates:
|
|
25
|
+
extends: .uv
|
|
26
|
+
stage: test
|
|
27
|
+
rules:
|
|
28
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
29
|
+
- if: $CI_PIPELINE_SOURCE == "push"
|
|
30
|
+
script:
|
|
31
|
+
# dev-зависимости у китов объявлены двумя способами — extra `dev` и группа
|
|
32
|
+
# `[dependency-groups] dev`; --all-extras покрывает первое, группу добавляем
|
|
33
|
+
# только если она объявлена, иначе uv падает «Group dev is not defined».
|
|
34
|
+
- uv sync --all-extras $(python3 -c "import tomllib;print('--group dev' if 'dev' in tomllib.load(open('pyproject.toml','rb')).get('dependency-groups',{}) else '')")
|
|
35
|
+
- uv run pytest tests/ -q
|
|
36
|
+
# Линтер по ВСЕМУ пакету, а не по списку файлов: сужение до «файлов волны 0»
|
|
37
|
+
# держалось девять китов и ни разу не было расширено — сужение приживается,
|
|
38
|
+
# расширение нет.
|
|
39
|
+
- uv run ruff check .
|
|
40
|
+
# Границы слоёв: контракт в .importlinter (ставится тем же шаблоном).
|
|
41
|
+
# — доп. пакеты через --with (пусто у большинства
|
|
42
|
+
# китов; adapterkit — пример кита, которому нужен сосед на прогоне
|
|
43
|
+
# lint-imports, см. apply_template.py --extra-with).
|
|
44
|
+
- uv run --with import-linter lint-imports
|
|
45
|
+
# Гейт 1, часть «согласованность локальных venv» (kitsctl doctor). Скрипт
|
|
46
|
+
# раздаётся шаблоном не всем: в раннере видно ОДИН репозиторий, соседних
|
|
47
|
+
# venv'ов там нет, — поэтому шаг условный и молча пропускается, когда
|
|
48
|
+
# инструмента у кита нет. Настоящее место doctor — локальный прогон перед
|
|
49
|
+
# тегом; см. docs/kits/SAFETY-NET.md, «Известные ограничения».
|
|
50
|
+
- 'if [ -f scripts/kitsctl.py ]; then python3 scripts/kitsctl.py doctor; else echo "kitsctl doctor — инструмента нет в репозитории, пропуск (см. SAFETY-NET.md)"; fi'
|
|
51
|
+
|
|
52
|
+
release-guard:
|
|
53
|
+
extends: .uv
|
|
54
|
+
stage: guard
|
|
55
|
+
needs: [gates]
|
|
56
|
+
rules:
|
|
57
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
58
|
+
script:
|
|
59
|
+
- python3 scripts/release_guard.py "$CI_COMMIT_TAG"
|
|
60
|
+
|
|
61
|
+
publish-pypi:
|
|
62
|
+
extends: .uv
|
|
63
|
+
stage: publish
|
|
64
|
+
needs: [release-guard]
|
|
65
|
+
rules:
|
|
66
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
67
|
+
id_tokens:
|
|
68
|
+
PYPI_ID_TOKEN:
|
|
69
|
+
aud: pypi
|
|
70
|
+
script:
|
|
71
|
+
- |
|
|
72
|
+
DIST=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])")
|
|
73
|
+
VER="${CI_COMMIT_TAG#v}"
|
|
74
|
+
if python3 - "$DIST" "$VER" <<'PY'
|
|
75
|
+
import json, sys, urllib.request
|
|
76
|
+
dist, ver = sys.argv[1], sys.argv[2]
|
|
77
|
+
try:
|
|
78
|
+
data = json.load(urllib.request.urlopen(f"https://pypi.org/pypi/{dist}/json"))
|
|
79
|
+
except Exception:
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
sys.exit(0 if ver in data.get("releases", {}) else 1)
|
|
82
|
+
PY
|
|
83
|
+
then echo "версия $VER уже на PyPI — публикацию пропускаем"; exit 0; fi
|
|
84
|
+
- uv build
|
|
85
|
+
# Последний рубеж перед необратимым шагом (версии на PyPI неизменяемы,
|
|
86
|
+
# yank — не удаление). Прецедент — leasekit: реальный email и домашний
|
|
87
|
+
# путь разработчика уехали в публичный индекс из тестовых фикстур. Ловит
|
|
88
|
+
# по классам, не по строкам; см. docs/kits/SAFETY-NET.md.
|
|
89
|
+
- python3 scripts/check_dist_leaks.py dist/
|
|
90
|
+
- |
|
|
91
|
+
export UV_PUBLISH_TOKEN=$(python3 - <<'PY'
|
|
92
|
+
import json, os, urllib.request
|
|
93
|
+
req = urllib.request.Request(
|
|
94
|
+
"https://pypi.org/_/oidc/mint-token",
|
|
95
|
+
data=json.dumps({"token": os.environ["PYPI_ID_TOKEN"]}).encode(),
|
|
96
|
+
headers={"Content-Type": "application/json"},
|
|
97
|
+
)
|
|
98
|
+
print(json.load(urllib.request.urlopen(req))["token"])
|
|
99
|
+
PY
|
|
100
|
+
)
|
|
101
|
+
- uv publish
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# СГЕНЕРИРОВАНО из devcontour/tools/kits/template/importlinter.ini — не правь
|
|
2
|
+
# здесь. Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
|
|
3
|
+
#
|
|
4
|
+
# Заготовка контракта слоёв. Запуск: `uv run --with import-linter lint-imports`.
|
|
5
|
+
#
|
|
6
|
+
# Контракт ниже — минимум, который годится ЛЮБОМУ киту и не требует ручной
|
|
7
|
+
# настройки: «skillkit не импортирует те киты экосистемы, которых нет в его
|
|
8
|
+
# объявленных зависимостях». Скрытая зависимость — это кит, который работает у
|
|
9
|
+
# автора (сосед стоит в venv) и разваливается у потребителя на первом импорте;
|
|
10
|
+
# именно так ломались pypi-publish и gemini-chat.
|
|
11
|
+
#
|
|
12
|
+
# Список forbidden_modules подставлен установщиком из реестра китов на момент
|
|
13
|
+
# установки: все известные пакеты китов МИНУС собственный МИНУС объявленные в
|
|
14
|
+
# pyproject зависимости. Появилась новая честная зависимость — объяви её в
|
|
15
|
+
# pyproject и переустанови шаблон, а не правь этот файл.
|
|
16
|
+
#
|
|
17
|
+
# Собственные контракты кита (layers/eager_forbidden/declared_dependencies,
|
|
18
|
+
# как у adapterkit) дописываются НИЖЕ отдельными секциями прямо в этом файле.
|
|
19
|
+
# Установщик сверяет файл целиком, поэтому `--check` после такой правки
|
|
20
|
+
# закономерно покажет расхождение с каноном — это ожидаемо и осознанно
|
|
21
|
+
# игнорируется в отчёте ревью, см. docs/kits/SAFETY-NET.md, «Что делать при
|
|
22
|
+
# расхождении» (п.2).
|
|
23
|
+
|
|
24
|
+
[importlinter]
|
|
25
|
+
# include_external_packages нужен: forbidden_modules перечисляет пакеты,
|
|
26
|
+
# внешние относительно root_packages.
|
|
27
|
+
include_external_packages = True
|
|
28
|
+
root_packages =
|
|
29
|
+
skillkit
|
|
30
|
+
exclude_type_checking_imports = True
|
|
31
|
+
|
|
32
|
+
[importlinter:contract:no-undeclared-kits]
|
|
33
|
+
name = skillkit не импортирует киты, которых нет в его зависимостях
|
|
34
|
+
type = forbidden
|
|
35
|
+
source_modules =
|
|
36
|
+
skillkit
|
|
37
|
+
forbidden_modules =
|
|
38
|
+
accountpoolkit
|
|
39
|
+
adapterkit
|
|
40
|
+
agentskit
|
|
41
|
+
aichatkit
|
|
42
|
+
authkit_client
|
|
43
|
+
authkit_contracts
|
|
44
|
+
authkit_server
|
|
45
|
+
b24kit
|
|
46
|
+
browserkit
|
|
47
|
+
chatkit
|
|
48
|
+
clientkit
|
|
49
|
+
corekit
|
|
50
|
+
emojikit
|
|
51
|
+
gitkit
|
|
52
|
+
installerkit
|
|
53
|
+
iokit
|
|
54
|
+
issuekit
|
|
55
|
+
leasekit
|
|
56
|
+
maxkit
|
|
57
|
+
modelkit
|
|
58
|
+
netkit
|
|
59
|
+
ormkit
|
|
60
|
+
persistkit
|
|
61
|
+
servicekit
|
|
62
|
+
sessionkit
|
|
63
|
+
socialkit
|
|
64
|
+
storagekit
|
|
65
|
+
totp_client
|
|
66
|
+
worker_service
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Исключения для секрет-скана s-skillgate перед push. Глобы — от корня репо.
|
|
2
|
+
#
|
|
3
|
+
# scripts/check_dist_leaks.py — сам гейт утечки личных данных (сетка
|
|
4
|
+
# безопасности китов, docs/kits/SAFETY-NET.md в devcontour), копия из
|
|
5
|
+
# tools/kits/template/check_dist_leaks.py. Ловит по классам, не по строкам:
|
|
6
|
+
# windows-путь с числовым именем пользователя, личный ящик на публичном
|
|
7
|
+
# провайдере, юникс-домашний путь. Regex-паттерны и пример подсказки
|
|
8
|
+
# (windows-путь с числовым account, «/home/user») по определению совпадают
|
|
9
|
+
# с собственным денилистом — иначе гейт не проверял бы то, ради чего написан (та же
|
|
10
|
+
# ситуация, что и с tests/golden/ у самого check_dist_leaks.py). Файл
|
|
11
|
+
# копируется буквально из канона — правки идут в шаблон, не сюда.
|
|
12
|
+
scripts/check_dist_leaks.py
|
|
@@ -4,7 +4,38 @@
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/ru/1.1.0/); версионирование —
|
|
5
5
|
[SemVer](https://semver.org/lang/ru/).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.5.0] — 2026-09-08
|
|
8
|
+
|
|
9
|
+
### Добавлено
|
|
10
|
+
|
|
11
|
+
- **`skillkit.installer_cli`** — установщик навыков переехал сюда из псевдокита
|
|
12
|
+
`s-installerkit` (волна 5, Atlas #2710). Зависимость объявлена ОПЦИОНАЛЬНЫМ
|
|
13
|
+
extra `installer_cli`: он тянет сеть, ключи и хранилище паролей, а базовый
|
|
14
|
+
`pip install s-skillkit` должен оставаться без них.
|
|
15
|
+
- Два сырых вызова подпроцесса в перенесённом коде переведены на `skillkit.proc.run`,
|
|
16
|
+
отсоединённый запуск самообновления вынесен в отдельный разрешённый модуль.
|
|
17
|
+
|
|
18
|
+
## [0.4.1] — 2026-09-01
|
|
19
|
+
|
|
20
|
+
### Fixed — `IAgentTarget` снова экспортируется из `skillkit.targets` (#2542)
|
|
21
|
+
|
|
22
|
+
Выпуск 0.4.0 уронил `skillery login` у пользователя с чистой установкой:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
from skillkit.targets import (
|
|
26
|
+
ImportError: cannot import name 'IAgentTarget' from 'skillkit.targets'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Протокол переименовали `IAgentTarget` → `AgentTarget` (lib#667) и положили
|
|
30
|
+
совместимое имя в `skillkit/targets/base.py`. Этого оказалось мало: чужой код
|
|
31
|
+
зовёт имя **из пакета**, а не из модуля `base`. Теперь алиас реэкспортируется
|
|
32
|
+
пакетом и объявлен в его `__all__`.
|
|
33
|
+
|
|
34
|
+
Почему не поймали заранее: у разработчика в окружении стоял кит 0.3.7 (там ещё
|
|
35
|
+
старое имя и нет нового), а `s-skillkit` тянется с верхней границей открытой —
|
|
36
|
+
пользователь получил 0.4.0 и упал. Сторож `tests/test_targets_public_names.py`
|
|
37
|
+
теперь проверяет ровно тот импорт, на котором падал клиент.
|
|
38
|
+
|
|
8
39
|
|
|
9
40
|
### Added — канон навыка переехал в кит: `skillkit.canon` (#162)
|
|
10
41
|
|
|
@@ -56,6 +87,27 @@
|
|
|
56
87
|
положил бы этот же навык сейчас. Повторная установка ТОГО ЖЕ навыка (в т.ч. с
|
|
57
88
|
новым entrypoint/config) проходит как раньше.
|
|
58
89
|
|
|
90
|
+
## [0.4.0] — 2026-09-01
|
|
91
|
+
|
|
92
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения между `v0.3.7` и `v0.4.0` — по истории коммитов:
|
|
93
|
+
|
|
94
|
+
- IAgentTarget → AgentTarget: контракт без венгерского префикса (lib#667) (ce15ab5)
|
|
95
|
+
- changelog навыка можно держать файлом, а не строкой в TOML (f31a516)
|
|
96
|
+
- skillkit: богатые поля frontmatter Claude доезжают до манифеста (#2430) (54b5c74)
|
|
97
|
+
- skillkit: плагин как единица поставки — манифест плюс реестр (#2431) (6298cdf)
|
|
98
|
+
- docs/adr: журнал архитектурных решений заведён (#807) (951b39f)
|
|
99
|
+
- Канон навыка переехал в кит: один источник вместо трёх (#162) (39d73ae)
|
|
100
|
+
|
|
101
|
+
## [0.3.7] — 2026-08-25
|
|
102
|
+
|
|
103
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения между `v0.3.6` и `v0.3.7` — по истории коммитов:
|
|
104
|
+
|
|
105
|
+
- fix(владение): CLI-команда и MCP-сервер не перехватываются чужим навыком (#2272) (6480860)
|
|
106
|
+
- test(владение): три случая перехвата артефактов + идемпотентность (#2272) (faf534b)
|
|
107
|
+
- docs(владение): CHANGELOG + докстринги модулей про гейт принадлежности (#2272) (5dc6183)
|
|
108
|
+
- merge(#2272): артефакт навыка не перехватывается чужим — команда и MCP-сервер под владением (d6cefbf)
|
|
109
|
+
- chore(release): s-skillkit 0.3.7 — владение артефактами навыка (6dcf7de)
|
|
110
|
+
|
|
59
111
|
## [0.3.6]
|
|
60
112
|
|
|
61
113
|
### Changed — своя политика запуска подпроцессов, s-librarykit убран (#1644)
|
|
@@ -157,6 +209,34 @@
|
|
|
157
209
|
`install_from_path` не изменилась. Миграция уже установленных сторов — на
|
|
158
210
|
стороне CLI (признак: `source == "local-path"` при непустом `skill_id`).
|
|
159
211
|
|
|
212
|
+
## [0.3.4] — 2026-07-28
|
|
213
|
+
|
|
214
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения между `v0.3.3` и `v0.3.4` — по истории коммитов:
|
|
215
|
+
|
|
216
|
+
- feat(telemetry): адаптер Skillery поверх нейтрального telemetrykit (0.3.4) (ea7390e)
|
|
217
|
+
|
|
218
|
+
## [0.3.3] — 2026-07-27
|
|
219
|
+
|
|
220
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения между `v0.3.2` и `v0.3.3` — по истории коммитов:
|
|
221
|
+
|
|
222
|
+
- feat: единый запуск процессов, .pkgsrc не течёт в зону агента, снапшот = hub (#1144 #1145 #1147) (1e5ac4a)
|
|
223
|
+
|
|
224
|
+
## [0.3.2] — 2026-07-22
|
|
225
|
+
|
|
226
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения между `v0.2.1` и `v0.3.2` — по истории коммитов:
|
|
227
|
+
|
|
228
|
+
- @ fix: монорепо-навык (skill_path) + preserved_paths из манифеста + детект менеджера (b8a9bda)
|
|
229
|
+
- fix(antigravity): цель писала в путь, который агент не читает (067c840)
|
|
230
|
+
- feat(hooks): навык объявляет хук в манифесте; путь .antigravity убран совсем (4d093a2)
|
|
231
|
+
- fix(targets)!: раскладки навыков по доказанной таблице (agy/opencode/codex) (1b6ec80)
|
|
232
|
+
- fix(remove)!: кит удаляет только своё + миграция старого канона Codex + лок (9cb7d60)
|
|
233
|
+
- fix(ownership)!: гейт принадлежности — свойство ОПЕРАЦИИ + сирота .antigravity (круг 3) (1a14015)
|
|
234
|
+
- fix(ownership): маркер принадлежности в мете + детектор устойчив к алиасам (круг 4) (678bd85)
|
|
235
|
+
- fix(ownership): улика принадлежности строго по маркеру + детектор ловит свои модули (880aa18)
|
|
236
|
+
- fix(ownership): migrate штампует маркер владения для legacy-каталогов (круг 5) (58ceb0a)
|
|
237
|
+
- fix(fetcher): git clone без видимого окна на Windows (CREATE_NO_WINDOW) (#1028) (ab4eaf5)
|
|
238
|
+
- feat(targets): единый источник путей агентов + antigravity=.agents/skills; CI PyPI (b6ff6a8)
|
|
239
|
+
|
|
160
240
|
## [0.3.1] — 2026-07-21
|
|
161
241
|
|
|
162
242
|
Круг 3 независимой проверки нашёл два настоящих дефекта в правках 0.3.0 и один
|
|
@@ -439,6 +519,27 @@ opencode её НЕ видит) доказывает, что модель не п
|
|
|
439
519
|
установки под ТЕКУЩУЮ ОС и список реально доступных альтернатив. ИИ-агент
|
|
440
520
|
может довести установку сам.
|
|
441
521
|
|
|
522
|
+
## [0.2.1] — 2026-07-20
|
|
523
|
+
|
|
524
|
+
Выпуск без отдельной записи в журнале на момент публикации. Изменения до тега `v0.2.1` — по истории коммитов:
|
|
525
|
+
|
|
526
|
+
- feat: skillkit v0.1.0 — локальное ядро управления навыками (вырезано из Skillery CLI, cli-kits W7); sibling librarykit, 0 внешних завязок (4aa8ee2)
|
|
527
|
+
- feat: гибрид-модули (path_store/tooling/deps_installer/mcp_register/collections) из Skillery CLI — добивка W7; инъекция bin/config-провайдеров (c77c495)
|
|
528
|
+
- fix(deps): pip-установка с --upgrade — зависимости поднимаются при апдейте (gap B) (5d76a64)
|
|
529
|
+
- fix(installer): пустой files-инвентарь не затирает стор при update (#272 BUG-3) (e7bcb7e)
|
|
530
|
+
- chore(release): s-skillkit 0.1.2 (фикс BUG-3 #272 — пустой files-инвентарь не затирает стор) (230ab79)
|
|
531
|
+
- feat(installer): subdir-навык — материализация подпапки репо (#268) (4a479f3)
|
|
532
|
+
- chore(release): s-skillkit 0.1.3 (#268 subdir-навык: материализация подпапки репо) (e80b7c9)
|
|
533
|
+
- fix(manifest): build_manifest несёт kind/cli/mcp/runtime_dependencies (#271) (8f88a86)
|
|
534
|
+
- test(manifest): регресс на tooling-поля + TOML-порядок runtime_dependencies (#271) (582fdaa)
|
|
535
|
+
- fix(tooling): runtime_deps-CLI в выделенный venv + шим целит туда же (#271) (df9f16e)
|
|
536
|
+
- feat(config): SkillkitConfig + configure() — единая брендо-конфигурация (0.2.0) (b4366b0)
|
|
537
|
+
- chore(release): s-skillkit 0.1.5 — версия в патч-серии 0.1.x (не 0.2.0) (c15beb2)
|
|
538
|
+
- feat(installer): install_from_snapshot — установка навыка из tar.gz-слепка (4f8c748)
|
|
539
|
+
- feat(installer): транзакционный install (#437) + release 0.1.7 (e236c94)
|
|
540
|
+
- fix(manifest): folded YAML scalar (>) в _read_frontmatter + bump 0.1.8 (c5d26c1)
|
|
541
|
+
- …и ещё 14 коммит(ов), см. `git log v0.2.1`
|
|
542
|
+
|
|
442
543
|
## [0.2.0] — 2026-07-15
|
|
443
544
|
|
|
444
545
|
### Added — install-kit (установка навыка «под ключ» со ВСЕМИ зависимостями)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: s-skillkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -10,9 +10,9 @@ Requires-Dist: pathspec>=0.12.0
|
|
|
10
10
|
Requires-Dist: platformdirs>=4.0
|
|
11
11
|
Requires-Dist: s-telemetrykit>=0.2.0
|
|
12
12
|
Requires-Dist: tomli-w>=1.0
|
|
13
|
-
Provides-Extra:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
13
|
+
Provides-Extra: installer-cli
|
|
14
|
+
Requires-Dist: s-clikit>=0.1.6; extra == 'installer-cli'
|
|
15
|
+
Requires-Dist: s-librarykit<0.9; extra == 'installer-cli'
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# s-skillkit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "s-skillkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.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" }
|
|
@@ -31,8 +31,30 @@ dependencies = [
|
|
|
31
31
|
"s-telemetrykit>=0.2.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
+
#: installer_cli (волна 5, #2710) — бывший псевдокит installerkit, консолидирован
|
|
35
|
+
#: как ``skillkit.installer_cli``. Тянет s-clikit (а с ним — httpx/keyring) и
|
|
36
|
+
#: s-librarykit (токен-стор installer_cli.tokens — ``librarykit.SecretStore``):
|
|
37
|
+
#: РОВНО тот сетевой/крипто-стек, который #1644 убрал из корня кита. Поэтому
|
|
38
|
+
#: extra, а НЕ dependencies: базовый `pip install s-skillkit` остаётся без
|
|
39
|
+
#: сети/auth; сетевой self-update/publish/токен-слой ставится явно —
|
|
40
|
+
#: `pip install s-skillkit[installer_cli]`. Модуль и не импортируется eagerly
|
|
41
|
+
#: из ``skillkit/__init__.py`` (тот же приём, что у ``skillkit.telemetry``).
|
|
34
42
|
[project.optional-dependencies]
|
|
35
|
-
|
|
43
|
+
installer_cli = ["s-clikit>=0.1.6", "s-librarykit<0.9"]
|
|
44
|
+
|
|
45
|
+
#: Инструменты прогона объявлены ГРУППОЙ, а не extra, намеренно (skl#2545).
|
|
46
|
+
#: extra ставится только по явному `--extra dev`; забыли флаг — pytest в
|
|
47
|
+
#: окружении нет, и `uv run pytest` молча берёт СИСТЕМНЫЙ, вместе с системными
|
|
48
|
+
#: версиями зависимостей. 01.09.2026 так и вышло: прогон шёл на Python 3.13 с
|
|
49
|
+
#: китом 0.3.6 вместо 3.14 и 0.4.1, то есть проверял не то, что собирается.
|
|
50
|
+
#: Группу `dev` uv синхронизирует сама, без флагов.
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8",
|
|
54
|
+
"pytest-asyncio>=0.24",
|
|
55
|
+
"ruff>=0.6",
|
|
56
|
+
"typing-extensions>=4.6",
|
|
57
|
+
]
|
|
36
58
|
|
|
37
59
|
[build-system]
|
|
38
60
|
requires = ["hatchling"]
|
|
@@ -63,6 +85,10 @@ ignore = ["RUF001", "RUF002", "RUF003", "RUF022", "RUF059"]
|
|
|
63
85
|
# модуля целиком, потому что `# noqa` внутри многострочного литерала уехал бы в
|
|
64
86
|
# сам вывод.
|
|
65
87
|
"skillkit/canon.py" = ["E501"]
|
|
88
|
+
# install_scripts.py (волна 5, #2710, бывший installerkit) — шаблоны bootstrap-
|
|
89
|
+
# скриптов содержат длинные shell/PowerShell one-liner'ы (напр. `irm ... | iex`),
|
|
90
|
+
# которые ДОЛЖНЫ оставаться одной строкой.
|
|
91
|
+
"skillkit/installer_cli/install_scripts.py" = ["E501"]
|
|
66
92
|
|
|
67
93
|
[tool.pytest.ini_options]
|
|
68
94
|
asyncio_mode = "auto"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Гейт публикации: артефакты не должны нести личные данные разработчика.
|
|
2
|
+
|
|
3
|
+
СГЕНЕРИРОВАНО из devcontour/tools/kits/template/check_dist_leaks.py — не правь
|
|
4
|
+
в ките. Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
|
|
5
|
+
Канонизирован из `leasekit/scripts/check_dist_leaks.py` (там появился первым,
|
|
6
|
+
как единственная у кита реальная защита публикации) — ревью волны 0 нашло,
|
|
7
|
+
что канон для всех китов её потерял, хотя случай, из-за которого она
|
|
8
|
+
написана, не специфичен для одного кита.
|
|
9
|
+
|
|
10
|
+
Зачем. Это уже случилось по-настоящему: в публичный PyPI уехали версии кита с
|
|
11
|
+
реальным email и домашним путём разработчика — они попали туда из ТЕСТОВЫХ
|
|
12
|
+
фикстур, которые никто не считал «настоящими данными». Удалить релиз с PyPI
|
|
13
|
+
нельзя (только yank, а окно удаления — 72 часа по PEP 763), поэтому цена
|
|
14
|
+
ошибки несимметрична: проверка стоит секунду, утечка необратима.
|
|
15
|
+
|
|
16
|
+
Ручная проверка перед каждым релизом не работает — её забывают ровно тогда,
|
|
17
|
+
когда релиз срочный. Поэтому она здесь, в CI, ДО шага публикации.
|
|
18
|
+
|
|
19
|
+
Что ловим (по классам, а не по конкретным строкам — иначе гейт защищает от
|
|
20
|
+
одного известного случая и слеп ко всем остальным):
|
|
21
|
+
|
|
22
|
+
1. **Windows-путь с числовым именем пользователя** — ``C:/Users/<цифры>/…``.
|
|
23
|
+
Числовое имя — почти всегда реальный аккаунт (у выдуманных пишут ``user``).
|
|
24
|
+
2. **Ящик на публичном почтовом провайдере** (gmail, yandex, mail.ru, …) —
|
|
25
|
+
именно так выглядела реальная утечка. Синтетику вроде ``x@y.io`` и ssh-URL
|
|
26
|
+
``git@github.com`` намеренно НЕ трогаем: гейт, который шумит на каждом
|
|
27
|
+
тестовом адресе, перестают читать, и он теряет смысл.
|
|
28
|
+
3. **Юникс-домашний путь** ``/home/<name>`` и ``/Users/<name>`` c именем,
|
|
29
|
+
не похожим на плейсхолдер.
|
|
30
|
+
|
|
31
|
+
Запуск::
|
|
32
|
+
|
|
33
|
+
python scripts/check_dist_leaks.py dist/
|
|
34
|
+
|
|
35
|
+
Код возврата 1 = найдено, публикацию продолжать нельзя.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
import tarfile
|
|
42
|
+
import zipfile
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
#: Публичные почтовые провайдеры. Ловим ТОЛЬКО их, а не «любой email»: в тестах
|
|
46
|
+
#: полно синтетических адресов (``x@y.io``, ``o@e.com``) и ssh-URL
|
|
47
|
+
#: (``git@github.com``) — гейт, орущий на них, быстро научатся игнорировать, и
|
|
48
|
+
#: он перестанет ловить настоящее. Реальная утечка выглядела ровно так:
|
|
49
|
+
#: живой ящик на gmail в тестовой фикстуре.
|
|
50
|
+
_MAIL_PROVIDERS = (
|
|
51
|
+
"gmail.com", "googlemail.com", "yandex.ru", "yandex.com", "ya.ru",
|
|
52
|
+
"mail.ru", "bk.ru", "inbox.ru", "list.ru", "internet.ru",
|
|
53
|
+
"outlook.com", "hotmail.com", "live.com", "msn.com",
|
|
54
|
+
"icloud.com", "me.com", "yahoo.com", "proton.me", "protonmail.com",
|
|
55
|
+
"rambler.ru", "aol.com", "gmx.com", "zoho.com",
|
|
56
|
+
)
|
|
57
|
+
#: Имена-плейсхолдеры в путях: их наличие означает «фикстура», а не живой аккаунт.
|
|
58
|
+
_SAFE_USER_NAMES = ("user", "testuser", "runner", "someone", "alice", "bob", "ci")
|
|
59
|
+
|
|
60
|
+
_RULES: tuple[tuple[str, re.Pattern[str]], ...] = (
|
|
61
|
+
(
|
|
62
|
+
"windows-путь с числовым именем пользователя",
|
|
63
|
+
re.compile(r"[Cc]:[\\/]+Users[\\/]+\d{3,}", re.IGNORECASE),
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
"личный ящик на публичном почтовом провайдере",
|
|
67
|
+
re.compile(
|
|
68
|
+
r"\b[\w.+-]+@(?:" + "|".join(d.replace(".", r"\.") for d in _MAIL_PROVIDERS) + r")\b",
|
|
69
|
+
re.IGNORECASE,
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
(
|
|
73
|
+
"домашний путь юникс",
|
|
74
|
+
# ``(?<![:\w])`` отсекает хвост windows-пути: в ``C:/Users/10001`` перед # leak-gate-allow
|
|
75
|
+
# ``/Users`` стоит двоеточие, и это дело windows-правила — иначе одна
|
|
76
|
+
# строка давала бы две жалобы подряд.
|
|
77
|
+
re.compile(r"(?<![:\w])/(?:home|Users)/([A-Za-z0-9._-]{3,})"),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
#: Маркер осознанного исключения. Ставится в ТОЙ ЖЕ строке и глушит гейт только
|
|
82
|
+
#: на ней. Нужен там, где «подозрительное» значение — суть теста: например,
|
|
83
|
+
#: фикстура денилист-гейта обязана содержать путь вида ``C:/Users/<цифры>``,
|
|
84
|
+
#: иначе тест не проверяет ровно то, ради чего написан. Пофайловый skip тут не
|
|
85
|
+
#: годится — он бы ослепил гейт на весь файл, включая будущие правки.
|
|
86
|
+
_ALLOW_MARKER = "leak-gate-allow"
|
|
87
|
+
|
|
88
|
+
#: Файлы, где совпадение — заведомо не утечка (списки исключений, метаданные
|
|
89
|
+
#: автора пакета клиент и так видит на странице PyPI).
|
|
90
|
+
_SKIP_SUFFIXES = (".gitignore", ".dockerignore", "METADATA", "PKG-INFO")
|
|
91
|
+
|
|
92
|
+
#: Каталоги-эталоны сканеров секретов. В `tests/golden/` у `s-skillgate` лежат
|
|
93
|
+
#: ОБРАЗЦЫ срабатываний правил — файл сверяется дословно, поэтому построчный
|
|
94
|
+
#: `leak-gate-allow` туда не вписать (он ломает сравнение), а без образцов
|
|
95
|
+
#: сканер нечем проверять. Исключение намеренно узкое: только этот подкаталог,
|
|
96
|
+
#: не весь `tests/` — настоящая утечка в прошлый раз пришла именно из обычного
|
|
97
|
+
#: тестового файла.
|
|
98
|
+
_SKIP_PATH_PARTS = ("/tests/golden/",)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _is_benign(rule: str, hit: str) -> bool:
|
|
102
|
+
low = hit.lower()
|
|
103
|
+
if rule.startswith("личный ящик"):
|
|
104
|
+
# ssh-URL вида git@github.com сюда не попадает (github.com не провайдер),
|
|
105
|
+
# но no-reply-адреса роботов формально живут на публичных доменах.
|
|
106
|
+
return low.startswith(("noreply@", "no-reply@", "git@"))
|
|
107
|
+
if rule.startswith("домашний"):
|
|
108
|
+
return any(n in low for n in _SAFE_USER_NAMES)
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _iter_members(path: Path):
|
|
113
|
+
"""(имя, текст) по каждому текстовому файлу архива."""
|
|
114
|
+
if path.name.endswith((".tar.gz", ".tgz")):
|
|
115
|
+
with tarfile.open(path) as tar:
|
|
116
|
+
for member in tar.getmembers():
|
|
117
|
+
if not member.isfile():
|
|
118
|
+
continue
|
|
119
|
+
fh = tar.extractfile(member)
|
|
120
|
+
if fh is None:
|
|
121
|
+
continue
|
|
122
|
+
yield member.name, fh.read()
|
|
123
|
+
elif path.suffix == ".whl":
|
|
124
|
+
with zipfile.ZipFile(path) as zf:
|
|
125
|
+
for name in zf.namelist():
|
|
126
|
+
if name.endswith("/"):
|
|
127
|
+
continue
|
|
128
|
+
yield name, zf.read(name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def scan(dist_dir: Path) -> list[str]:
|
|
132
|
+
problems: list[str] = []
|
|
133
|
+
archives = [p for p in dist_dir.iterdir() if p.suffix == ".whl" or p.name.endswith(".tar.gz")]
|
|
134
|
+
if not archives:
|
|
135
|
+
raise SystemExit(f"в {dist_dir} нет артефактов — нечего проверять")
|
|
136
|
+
for archive in archives:
|
|
137
|
+
for name, raw in _iter_members(archive):
|
|
138
|
+
if name.endswith(_SKIP_SUFFIXES):
|
|
139
|
+
continue
|
|
140
|
+
norm = "/" + name.replace("\\", "/").lstrip("/")
|
|
141
|
+
if any(part in norm for part in _SKIP_PATH_PARTS):
|
|
142
|
+
continue
|
|
143
|
+
text = raw.decode("utf-8", "ignore")
|
|
144
|
+
# Построчно: allow-маркер действует ровно на свою строку, и в отчёт
|
|
145
|
+
# попадает номер — иначе искать совпадение в файле на 5000 строк.
|
|
146
|
+
for lineno, line in enumerate(text.splitlines(), 1):
|
|
147
|
+
if _ALLOW_MARKER in line:
|
|
148
|
+
continue
|
|
149
|
+
for rule, pattern in _RULES:
|
|
150
|
+
for match in pattern.finditer(line):
|
|
151
|
+
shown = match.group(0)
|
|
152
|
+
# Для правила с группой (домашний путь) судим по имени
|
|
153
|
+
# пользователя, для остальных — по совпадению целиком.
|
|
154
|
+
subject = match.group(1) if match.groups() else shown
|
|
155
|
+
if _is_benign(rule, subject):
|
|
156
|
+
continue
|
|
157
|
+
problems.append(
|
|
158
|
+
f"{archive.name} :: {name}:{lineno} :: {rule} :: {shown}"
|
|
159
|
+
)
|
|
160
|
+
return sorted(set(problems))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main() -> int:
|
|
164
|
+
dist_dir = Path(sys.argv[1] if len(sys.argv) > 1 else "dist")
|
|
165
|
+
problems = scan(dist_dir)
|
|
166
|
+
if problems:
|
|
167
|
+
print("НАЙДЕНЫ личные данные в артефактах — публикация остановлена:\n")
|
|
168
|
+
for line in problems:
|
|
169
|
+
print(" •", line)
|
|
170
|
+
print(
|
|
171
|
+
"\nЗамените реальные значения на плейсхолдеры "
|
|
172
|
+
"(user@example.com, C:/Users/10001, /home/user)." # leak-gate-allow
|
|
173
|
+
)
|
|
174
|
+
return 1
|
|
175
|
+
print(f"артефакты в {dist_dir} чисты: личных данных не найдено")
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
raise SystemExit(main())
|