@7n/rules 1.14.1 → 1.16.0
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.
- package/CHANGELOG.md +12 -0
- package/package.json +2 -1
- package/rules/k8s/dremio_logging/concern.json +8 -0
- package/rules/k8s/dremio_logging/dremio_logging.mdc +31 -0
- package/rules/k8s/dremio_logging/dremio_logging.rego +86 -0
- package/scripts/lib/docs/index.md +4 -1
- package/scripts/lib/docs/plugin-api.md +31 -0
- package/scripts/lib/docs/resolve-plugins.md +4 -4
- package/scripts/lib/plugin-api.mjs +71 -0
- package/scripts/lib/resolve-plugins.mjs +31 -6
- package/skills/taze/SKILL.md +8 -1
- package/skills/taze/js/docs/index.md +6 -6
- package/skills/taze/js/docs/orchestrate.md +12 -17
- package/skills/taze/js/docs/rust-provider.md +42 -0
- package/skills/taze/js/orchestrate.mjs +113 -334
- package/skills/taze/js/rust-provider.mjs +162 -0
- package/skills/taze/js/docs/uv-diff.md +0 -41
- package/skills/taze/js/uv-diff.mjs +0 -176
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.16.0] - 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- k8s: новий концерн dremio_logging — logback.xml Dremio Helm-чарту має містити WARN-оверрайди для шести відомо-шумних loggerів (rego, XML через conftest, detection-only)
|
|
8
|
+
|
|
9
|
+
## [1.15.0] - 2026-07-18
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Плагінна архітектура мовних екосистем (фаза 1 spec lang-plugins-extraction): новий порт `EcosystemProvider` у `@7n/rules/plugin-api` (detect/available/backup/bump/diff/promptFor/cleanup + `assertEcosystemProvider`), taze-оркестратор веде не-npm гілки генеричним циклом по провайдерах — Rust як вбудований first-party (`rust-provider.mjs`), Python/uv виїхав у окремий плагін `@7n/rules-lang-python` (автодетект за кореневим pyproject.toml, extension-point `taze` — перший реальний споживач handlers-API). Плагін без rules/ із `contributes.rules:false` тепер легальний (лише handlers); провал однієї екосистеми не зупиняє інших
|
|
14
|
+
|
|
3
15
|
## [1.14.1] - 2026-07-18
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/rules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"exports": {
|
|
52
52
|
".": "./bin/n-rules.js",
|
|
53
53
|
"./package.json": "./package.json",
|
|
54
|
+
"./plugin-api": "./scripts/lib/plugin-api.mjs",
|
|
54
55
|
"./scripts/*": "./scripts/*",
|
|
55
56
|
"./schemas/*": "./schemas/*",
|
|
56
57
|
"./rules/*": "./rules/*"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## WARN-оверрайди шумних loggerів у logback.xml Dremio
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `k8s.dremio_logging`
|
|
4
|
+
|
|
5
|
+
**Цільові файли:** `**/dremio_v2/config/logback.xml` — vendored-конфіг логування Dremio Helm-чарту. Це XML (не YAML): conftest парсить його нативно (парсер інферується за розширенням). Кожна env-копія (`dev/dremio_v2/…`, `ua/dremio_v2/…` тощо) перевіряється окремо — без крос-файлової дедуплікації. Cross-file gating (як у `hasura_configmap`) не потрібен: шлях сам ідентифікує Dremio-чарт.
|
|
6
|
+
|
|
7
|
+
**Що перевіряється:** для кожного FQCN зі списку нижче має існувати `<logger name="<FQCN>" level="warn"/>` (рівень case-insensitive; «строгіше» — `error`/`off` — теж ок). Кожен запис-дублікат для FQCN має задовольняти вимогу окремо (конфліктні дублікати — порушення: logback бере останній, конфіг має бути однозначним).
|
|
8
|
+
|
|
9
|
+
| FQCN |
|
|
10
|
+
|------|
|
|
11
|
+
| `com.dremio.sabot.exec.fragment.FragmentExecutor` |
|
|
12
|
+
| `com.dremio.sabot.exec.fragment.FragmentStatusReporter` |
|
|
13
|
+
| `com.dremio.sabot.exec.QueryTicket` |
|
|
14
|
+
| `com.dremio.service.reflection.descriptor.MaterializationCache` |
|
|
15
|
+
| `com.dremio.exec.planner.plancache.PlanCacheSynchronizer` |
|
|
16
|
+
| `com.dremio.exec.planner.plancache.CacheRefresher` |
|
|
17
|
+
|
|
18
|
+
**Чому:** ці loggerи (усі на одному `planning-cache-refresher` треді; підтверджено живою перевіркою в проді) пишуть INFO на кожен запит/фоновий цикл і домінують обсяг логів у Cloud Logging без діагностичної цінності понад WARN. Список фіксований — не розширюй «про запас»; нові loggerи — окремою ітерацією.
|
|
19
|
+
|
|
20
|
+
**Приклад валідного фрагмента:**
|
|
21
|
+
|
|
22
|
+
```xml
|
|
23
|
+
<logger name="com.dremio.sabot.exec.fragment.FragmentExecutor" level="warn"/>
|
|
24
|
+
<logger name="com.dremio.sabot.exec.fragment.FragmentStatusReporter" level="warn"/>
|
|
25
|
+
<logger name="com.dremio.sabot.exec.QueryTicket" level="warn"/>
|
|
26
|
+
<logger name="com.dremio.service.reflection.descriptor.MaterializationCache" level="warn"/>
|
|
27
|
+
<logger name="com.dremio.exec.planner.plancache.PlanCacheSynchronizer" level="warn"/>
|
|
28
|
+
<logger name="com.dremio.exec.planner.plancache.CacheRefresher" level="warn"/>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Autofix:** відсутній (detection-only, як `hasura_httproute`) — XML-трансформ складніший за YAML `setIn`; T0-fix, за потреби, окремою ітерацією.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# WARN-оверрайди шумних loggerів у logback.xml Dremio Helm-чарту.
|
|
2
|
+
#
|
|
3
|
+
# Проблема: перелічені нижче FQCN (усі живуть на одному `planning-cache-refresher`
|
|
4
|
+
# треді) пишуть INFO-логи на кожен запит/фоновий цикл і домінують обсяг у
|
|
5
|
+
# Cloud Logging без діагностичної цінності понад WARN. Вимога: у кожній копії
|
|
6
|
+
# vendored-конфігу `dremio_v2/config/logback.xml` (наприклад `dev/…` і `ua/…` —
|
|
7
|
+
# кожна env-копія перевіряється окремо, без крос-файлової дедуплікації) має бути
|
|
8
|
+
# `<logger name="<FQCN>" level="warn"/>` для кожного FQCN зі списку
|
|
9
|
+
# `required_loggers`, з рівнем "warn" або строгішим ("error", "off").
|
|
10
|
+
#
|
|
11
|
+
# Вхід — XML: conftest інферить парсер за розширенням `.xml`. Конвенція
|
|
12
|
+
# представлення (див. `conftest parse`):
|
|
13
|
+
# - атрибути мають префікс "-" (`-name`, `-level`);
|
|
14
|
+
# - один дочірній елемент → об'єкт, кілька → масив. Тому `<logger>`
|
|
15
|
+
# нормалізується у `loggers` (завжди масив).
|
|
16
|
+
#
|
|
17
|
+
# Запуск (локально):
|
|
18
|
+
# conftest test path/to/dremio_v2/config/logback.xml \
|
|
19
|
+
# -p npm/rules/k8s/dremio_logging \
|
|
20
|
+
# --namespace k8s.dremio_logging
|
|
21
|
+
#
|
|
22
|
+
# Список FQCN підтверджено живою перевіркою в проді — не розширюй «про запас».
|
|
23
|
+
#
|
|
24
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
25
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
26
|
+
package k8s.dremio_logging
|
|
27
|
+
|
|
28
|
+
import rego.v1
|
|
29
|
+
|
|
30
|
+
# Обов'язкові WARN-оверрайди (FQCN відомо-шумних loggerів Dremio).
|
|
31
|
+
required_loggers := {
|
|
32
|
+
"com.dremio.sabot.exec.fragment.FragmentExecutor",
|
|
33
|
+
"com.dremio.sabot.exec.fragment.FragmentStatusReporter",
|
|
34
|
+
"com.dremio.sabot.exec.QueryTicket",
|
|
35
|
+
"com.dremio.service.reflection.descriptor.MaterializationCache",
|
|
36
|
+
"com.dremio.exec.planner.plancache.PlanCacheSynchronizer",
|
|
37
|
+
"com.dremio.exec.planner.plancache.CacheRefresher",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Рівні logback, що задовольняють вимогу «warn або строгіше».
|
|
41
|
+
allowed_levels := {"warn", "error", "off"}
|
|
42
|
+
|
|
43
|
+
# Нормалізація <logger>: один елемент XML-парсер віддає об'єктом, кілька — масивом.
|
|
44
|
+
# Тіла взаємовиключні (is_array/is_object), default покриває відсутність елемента
|
|
45
|
+
# (зокрема logback.xml без <configuration>-кореня → порожній список → deny на
|
|
46
|
+
# кожен обов'язковий FQCN).
|
|
47
|
+
default loggers := []
|
|
48
|
+
|
|
49
|
+
loggers := input.configuration.logger if is_array(input.configuration.logger)
|
|
50
|
+
|
|
51
|
+
loggers := [input.configuration.logger] if is_object(input.configuration.logger)
|
|
52
|
+
|
|
53
|
+
# Усі записи <logger> для заданого FQCN (записи без атрибута name пропускаються).
|
|
54
|
+
entries_for(name) := [l |
|
|
55
|
+
some l in loggers
|
|
56
|
+
l["-name"] == name
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
# Нормалізований рівень запису; undefined, якщо атрибут level відсутній або не рядок.
|
|
60
|
+
entry_level(l) := lower(trim_space(l["-level"])) if is_string(l["-level"])
|
|
61
|
+
|
|
62
|
+
# Запис задовольняє вимогу: level читається і є warn/error/off.
|
|
63
|
+
entry_ok(l) if entry_level(l) in allowed_levels
|
|
64
|
+
|
|
65
|
+
# FQCN узагалі не має <logger>-оверрайду.
|
|
66
|
+
deny contains msg if {
|
|
67
|
+
some name in required_loggers
|
|
68
|
+
count(entries_for(name)) == 0
|
|
69
|
+
msg := sprintf(
|
|
70
|
+
"logback.xml: додай <logger name=\"%s\" level=\"warn\"/> — шумний Dremio-logger заливає Cloud Logging (k8s.mdc)",
|
|
71
|
+
[name],
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Оверрайд є, але слабший за warn (info/debug/trace), без level або з не-рядковим
|
|
76
|
+
# level. Вимога до КОЖНОГО запису з цим FQCN: дублікати з конфліктними рівнями
|
|
77
|
+
# (logback бере останній) — теж порушення, конфіг має бути однозначним.
|
|
78
|
+
deny contains msg if {
|
|
79
|
+
some name in required_loggers
|
|
80
|
+
some l in entries_for(name)
|
|
81
|
+
not entry_ok(l)
|
|
82
|
+
msg := sprintf(
|
|
83
|
+
"logback.xml: <logger name=\"%s\"> має рівень %v — постав level=\"warn\" або строгіше (error/off) (k8s.mdc)",
|
|
84
|
+
[name, object.get(l, "-level", "«відсутній»")],
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -6,6 +6,7 @@ resource: npm/scripts/lib/
|
|
|
6
6
|
|
|
7
7
|
| Файл | Тип |
|
|
8
8
|
| --------------------------------------------------------------------------- | --------- |
|
|
9
|
+
| [acp-runner.mjs](acp-runner.md) | JS Module |
|
|
9
10
|
| [assert-project-root.mjs](assert-project-root.md) | JS Module |
|
|
10
11
|
| [blue-oak.mjs](blue-oak.md) | JS Module |
|
|
11
12
|
| [changed-files.mjs](changed-files.md) | JS Module |
|
|
@@ -23,7 +24,9 @@ resource: npm/scripts/lib/
|
|
|
23
24
|
| [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
|
|
24
25
|
| [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
|
|
25
26
|
| [mirror-parity.mjs](mirror-parity.md) | JS Module |
|
|
26
|
-
| [
|
|
27
|
+
| [plugin-api.mjs](plugin-api.md) | JS Module |
|
|
28
|
+
| [read-n-rules-config-lite.mjs](read-n-rules-config-lite.md) | JS Module |
|
|
29
|
+
| [resolve-plugins.mjs](resolve-plugins.md) | JS Module |
|
|
27
30
|
| [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
|
|
28
31
|
| [root-notice.mjs](root-notice.md) | JS Module |
|
|
29
32
|
| [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: plugin-api.mjs
|
|
4
|
+
resource: npm/scripts/lib/plugin-api.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: dc4f4a12
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
tier: local-min-retry
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.98
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Публічний API для плагінів `@7n/rules` (експорт `@7n/rules/plugin-api`). У фазі 1 (`spec 2026-07-18-lang-plugins-extraction`) реалізовано порт `EcosystemProvider` для `taze`. Плагін реєструє провайдера через маніфест `package.json`: `"n-rules": { "contributes": { "handlers": { "taze": "./taze/provider.mjs" } } }`, причому модуль-обробник експортує об'єкт провайдера як `default`. Публічні функції включають `PLUGIN_API_VERSION` та `assertEcosystemProvider`. Ядро реекспортує Semver-утиліти для коректної класифікації major/minor версій плагінами, використовуючи той самий принцип caret-семантики, що й ядро, без дублювання коду та імпорту внутрішніх шляхів `@7n/rules`.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
- PLUGIN_API_VERSION визначає версію контракту plugin-api.
|
|
21
|
+
- assertEcosystemProvider перевіряє, чи об'єкт, наданий плагіном, відповідає очікуваній структурі EcosystemProvider, і викидає відповідну помилку, якщо він не валідний.
|
|
22
|
+
|
|
23
|
+
## Публічний API
|
|
24
|
+
|
|
25
|
+
- PLUGIN_API_VERSION — версія контракту plugin-api; плагін декларує `requiresPluginApi`, несумісність дає зрозумілий skip, не креш.
|
|
26
|
+
- isBreaking / parseVersion — реекспорт semver-утиліт ядра для однакової класифікації major/minor у плагінах.
|
|
27
|
+
- assertEcosystemProvider — перевіряє, чи має default-експорт handler-модуля плагіна необхідну форму EcosystemProvider, щоб система могла виявити помилку чітко замість непередбачуваної.
|
|
28
|
+
|
|
29
|
+
## Гарантії поведінки
|
|
30
|
+
|
|
31
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -3,15 +3,15 @@ type: JS Module
|
|
|
3
3
|
title: resolve-plugins.mjs
|
|
4
4
|
resource: npm/scripts/lib/resolve-plugins.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: ef33c272
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Резолв плагінів @7n/rules: визначає, які пакети-плагіни активні у проєкті, де їхні `rules/`-каталоги, які capabilities вони надають і які handlers надають.
|
|
10
10
|
|
|
11
|
-
Джерело правди — поле `plugins: string[]` у `.n-rules.json`; воно завжди перекриває автодетект, а явний порожній масив означає «плагіни вимкнено». Якщо поля немає, `detectPluginsFromRepo` шукає файлові сигнали: наявність yml у `.github/workflows/` дає `@7n/rules-ci-github`, файл `azure-pipelines.yml` у корені — `@7n/rules-ci-azure` (реєстр `KNOWN_CI_PLUGINS`). Лише коли файлових
|
|
11
|
+
Джерело правди — поле `plugins: string[]` у `.n-rules.json`; воно завжди перекриває автодетект, а явний порожній масив означає «плагіни вимкнено». Якщо поля немає, `detectPluginsFromRepo` шукає файлові сигнали: наявність yml у `.github/workflows/` дає `@7n/rules-ci-github`, файл `azure-pipelines.yml` у корені — `@7n/rules-ci-azure` (реєстр `KNOWN_CI_PLUGINS`). Лише коли файлових CI-сигналів немає, вмикається fallback за `repository.url` кореневого package.json (`github.com` → github, `dev.azure.com`/`visualstudio.com` → azure). Обидва сигнали дають обидва плагіни, жодного — порожній список. Окремо від CI детектяться мовні плагіни (реєстр `KNOWN_LANG_PLUGINS`, лише файлові сигнали, без URL-fallback): кореневий `pyproject.toml` → `@7n/rules-lang-python`.
|
|
12
12
|
|
|
13
13
|
`ensurePluginInstalled` ставить відсутній плагін через `bun add -d` (пакет стає devDependency — зміна видима у diff). Будь-який фейл установки (offline, пакет не опублікований) — warning і graceful skip, ніколи не hard-fail: лінт і синк мають працювати без мережі.
|
|
14
14
|
|
|
15
|
-
`resolvePlugins(projectRoot, config, options)` — головна функція: повертає масив `{name, packageRoot, rulesDir, manifest}` доступних плагінів з кешем на процес. `options.allowInstall: false` — hot-path режим (hook, lint): лише вже встановлені пакети, без `bun add`; `options.quiet: true` глушить warning-и (hook викликається на кожен файл).
|
|
15
|
+
`resolvePlugins(projectRoot, config, options)` — головна функція: повертає масив `{name, packageRoot, rulesDir, manifest}` доступних плагінів з кешем на процес. `options.allowInstall: false` — hot-path режим (hook, lint): лише вже встановлені пакети, без `bun add`; `options.quiet: true` глушить warning-и (hook викликається на кожен файл). Плагін, що декларує правила (`contributes.rules !== false`), але не має каталогу `rules/`, пропускається як битий; плагін із явним `contributes.rules: false` (лише handlers, як `lang-*`) — легальний, `resolveRulesDirs` його просто не включає у джерела правил.
|
|
16
16
|
|
|
17
|
-
Маніфест — блок `"n-rules"` у package.json плагіна: `capabilities` (масив рядків на кшталт `ci:github`, живлять гейт концернів `requires.capability`) і `contributes.handlers` (мапа extension-point → відносний шлях модуля). `getActiveCapabilities` агрегує capabilities усіх плагінів у Set; `getHandlers(point)` повертає абсолютні шляхи модулів-обробників (
|
|
17
|
+
Маніфест — блок `"n-rules"` у package.json плагіна: `capabilities` (масив рядків на кшталт `ci:github`, живлять гейт концернів `requires.capability`) і `contributes.handlers` (мапа extension-point → відносний шлях модуля). `getActiveCapabilities` агрегує capabilities усіх плагінів у Set; `getHandlers(point)` повертає абсолютні шляхи модулів-обробників (перший реальний споживач — taze-оркестратор, extension-point `taze`). `resolveRulesDirs` віддає впорядковані джерела правил: ядро завжди перше (його правила й концерни виграють колізії), далі плагіни у порядку списку. `clearPluginResolveCache` скидає кеш (для тестів).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Публічний API для плагінів `@7n/rules` (експорт `@7n/rules/plugin-api`).
|
|
3
|
+
*
|
|
4
|
+
* Фаза 1 (spec 2026-07-18-lang-plugins-extraction): один порт — `EcosystemProvider`
|
|
5
|
+
* для taze. Плагін реєструє провайдера через маніфест package.json:
|
|
6
|
+
* `"n-rules": { "contributes": { "handlers": { "taze": "./taze/provider.mjs" } } }`;
|
|
7
|
+
* модуль-обробник експортує обʼєкт провайдера як `default`. Наступні порти
|
|
8
|
+
* (doc-files, lint) додаються окремими фазами — не проєктуються наперед.
|
|
9
|
+
*
|
|
10
|
+
* Semver-утиліти ядра реекспортуються звідси, щоб плагіни класифікували
|
|
11
|
+
* major/minor за тим самим правилом caret-семантики, що й ядро, без
|
|
12
|
+
* дублювання коду і без імпорту внутрішніх шляхів `@7n/rules`.
|
|
13
|
+
*/
|
|
14
|
+
export { isBreaking, parseVersion } from '../../skills/taze/js/diff.mjs'
|
|
15
|
+
|
|
16
|
+
/** Версія контракту plugin-api: плагін декларує `requiresPluginApi`, несумісність → skip, не креш. */
|
|
17
|
+
export const PLUGIN_API_VERSION = 1
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {(cmd: string, args: string[], opts?: object) => { status: number|null, stdout: string, stderr: string }} SpawnFn
|
|
21
|
+
* Сумісний зі `spawnSync` виклик зовнішньої команди (інжектовний у тестах).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} EcosystemAvailability
|
|
26
|
+
* @property {boolean} ok чи доступний тулчейн екосистеми (напр. `uv --version` → exit 0)
|
|
27
|
+
* @property {string|null} reason зрозуміла людині причина недоступності для звіту (null, якщо ok)
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} EcosystemProvider
|
|
32
|
+
* Порт однієї екосистеми залежностей для taze-оркестратора. Ядро викликає методи
|
|
33
|
+
* в порядку: `detect` → (`available`) → `backup` → `bump` → `diff` → по кожному
|
|
34
|
+
* major-запису `promptFor` → `cleanup`. Провал `bump`/`diff` одного провайдера
|
|
35
|
+
* не зупиняє інших.
|
|
36
|
+
* @property {string} id стабільний ідентифікатор (напр. `python-uv`)
|
|
37
|
+
* @property {string} title заголовок секції звіту (напр. `Python-пакети (uv)`)
|
|
38
|
+
* @property {string} manifestNoun назва маніфеста для звіту (напр. `pyproject.toml`)
|
|
39
|
+
* @property {string} skillSection посилання на ручну гілку SKILL.md у звіті-skip (напр. `Python-гілкою SKILL.md`)
|
|
40
|
+
* @property {(cwd: string, deps: { spawnFn: SpawnFn }) => string[]} detect відносні шляхи знайдених маніфестів (порожньо → екосистеми в проєкті немає, тиша у звіті)
|
|
41
|
+
* @property {(spawnFn: SpawnFn) => EcosystemAvailability} available чи встановлений тулчейн; `ok:false` → graceful skip із `reason` у звіті
|
|
42
|
+
* @property {(cwd: string, manifests: string[], deps: object) => Promise<void>} backup бекап маніфестів/lock-файлів (`<file>.taze-bak`)
|
|
43
|
+
* @property {(cwd: string, manifests: string[], ctx: { spawnFn: SpawnFn, log: (line: string) => void, deps: object }) => Promise<void>} bump масовий bump до latest (включно з major)
|
|
44
|
+
* @property {(cwd: string, manifests: string[], deps: object) => Promise<{major: Array<{manifest: string, pkg: string, from: string, to: string}>, minorPatch: number, totalChanged: number}>} diff детермінована класифікація major vs minor/patch (бекап vs поточний)
|
|
45
|
+
* @property {(entry: {manifest: string, pkg: string, from: string, to: string}) => string} promptFor промпт одного ізольованого виклику раннера для одного major-запису
|
|
46
|
+
* @property {(cwd: string, manifests: string[], deps: object) => Promise<void>} cleanup прибирання бекапів
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const REQUIRED_PROVIDER_FUNCTIONS = ['detect', 'available', 'backup', 'bump', 'diff', 'promptFor', 'cleanup']
|
|
50
|
+
const REQUIRED_PROVIDER_STRINGS = ['id', 'title', 'manifestNoun', 'skillSection']
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Валідує форму провайдера з модуля плагіна — зрозуміла помилка замість
|
|
54
|
+
* «undefined is not a function» глибоко в оркестраторі.
|
|
55
|
+
* @param {unknown} candidate default-експорт handler-модуля плагіна
|
|
56
|
+
* @param {string} source ім'я плагіна/шлях модуля для повідомлення
|
|
57
|
+
* @returns {EcosystemProvider} той самий обʼєкт, якщо валідний
|
|
58
|
+
*/
|
|
59
|
+
export function assertEcosystemProvider(candidate, source) {
|
|
60
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
61
|
+
throw new TypeError(`plugin-api: ${source} — default-експорт не є обʼєктом EcosystemProvider`)
|
|
62
|
+
}
|
|
63
|
+
const missing = [
|
|
64
|
+
...REQUIRED_PROVIDER_STRINGS.filter(k => typeof candidate[k] !== 'string' || candidate[k] === ''),
|
|
65
|
+
...REQUIRED_PROVIDER_FUNCTIONS.filter(k => typeof candidate[k] !== 'function')
|
|
66
|
+
]
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
throw new TypeError(`plugin-api: ${source} — EcosystemProvider без обовʼязкових полів: ${missing.join(', ')}`)
|
|
69
|
+
}
|
|
70
|
+
return /** @type {EcosystemProvider} */ (candidate)
|
|
71
|
+
}
|
|
@@ -22,12 +22,17 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
|
22
22
|
import { spawnSync } from 'node:child_process'
|
|
23
23
|
import { join, resolve } from 'node:path'
|
|
24
24
|
|
|
25
|
-
/** Відомі
|
|
25
|
+
/** Відомі CI-плагіни для автовизначення: сигнал у дереві репо → npm-пакет. */
|
|
26
26
|
export const KNOWN_CI_PLUGINS = Object.freeze({
|
|
27
27
|
github: '@7n/rules-ci-github',
|
|
28
28
|
azure: '@7n/rules-ci-azure'
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
+
/** Відомі мовні плагіни: файловий сигнал екосистеми в корені репо → npm-пакет. */
|
|
32
|
+
export const KNOWN_LANG_PLUGINS = Object.freeze({
|
|
33
|
+
python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python' }
|
|
34
|
+
})
|
|
35
|
+
|
|
31
36
|
const WORKFLOW_YML_RE = /\.ya?ml$/u
|
|
32
37
|
const GITHUB_URL_RE = /github\.com/iu
|
|
33
38
|
const AZURE_URL_RE = /dev\.azure\.com|visualstudio\.com/iu
|
|
@@ -72,9 +77,9 @@ function hasGithubWorkflows(projectRoot) {
|
|
|
72
77
|
* Файлові сигнали мають пріоритет; `repository.url` — лише коли файлових сигналів нема
|
|
73
78
|
* (свіже репо без CI-конфігів). Обидва сигнали → обидва плагіни; жодного → [].
|
|
74
79
|
* @param {string} projectRoot корінь репозиторію
|
|
75
|
-
* @returns {string[]} npm-імена
|
|
80
|
+
* @returns {string[]} npm-імена CI-плагінів
|
|
76
81
|
*/
|
|
77
|
-
|
|
82
|
+
function detectCiPlugins(projectRoot) {
|
|
78
83
|
const out = []
|
|
79
84
|
if (hasGithubWorkflows(projectRoot)) out.push(KNOWN_CI_PLUGINS.github)
|
|
80
85
|
if (existsSync(join(projectRoot, 'azure-pipelines.yml'))) out.push(KNOWN_CI_PLUGINS.azure)
|
|
@@ -88,6 +93,21 @@ export function detectPluginsFromRepo(projectRoot) {
|
|
|
88
93
|
return out
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Автодетект плагінів за станом репозиторію: CI-плагіни (файлові сигнали з
|
|
98
|
+
* fallback на `repository.url`) + мовні плагіни (лише файлові сигнали —
|
|
99
|
+
* маніфест екосистеми в корені; URL-fallback для мов безглуздий).
|
|
100
|
+
* @param {string} projectRoot корінь репозиторію
|
|
101
|
+
* @returns {string[]} npm-імена плагінів
|
|
102
|
+
*/
|
|
103
|
+
export function detectPluginsFromRepo(projectRoot) {
|
|
104
|
+
const out = detectCiPlugins(projectRoot)
|
|
105
|
+
for (const { signal, pkg } of Object.values(KNOWN_LANG_PLUGINS)) {
|
|
106
|
+
if (existsSync(join(projectRoot, signal))) out.push(pkg)
|
|
107
|
+
}
|
|
108
|
+
return out
|
|
109
|
+
}
|
|
110
|
+
|
|
91
111
|
/**
|
|
92
112
|
* Список плагінів проєкту: явний `config.plugins` (включно з порожнім = вимкнено) або автодетект.
|
|
93
113
|
* @param {string} projectRoot корінь репозиторію
|
|
@@ -183,12 +203,15 @@ export function resolvePlugins(projectRoot, config, options = {}) {
|
|
|
183
203
|
}
|
|
184
204
|
continue
|
|
185
205
|
}
|
|
206
|
+
const manifest = readPluginManifest(packageRoot)
|
|
186
207
|
const rulesDir = join(packageRoot, 'rules')
|
|
187
|
-
if (!existsSync(rulesDir)) {
|
|
208
|
+
if (manifest.contributes.rules && !existsSync(rulesDir)) {
|
|
209
|
+
// Плагін ДЕКЛАРУЄ правила (rules !== false), але каталогу нема — битий пакет.
|
|
210
|
+
// Плагін без правил (лише handlers, напр. lang-* до фази 3) — легальний.
|
|
188
211
|
if (options.quiet !== true) console.warn(`⚠️ Плагін ${name} без каталогу rules/ — пропускаю\n`)
|
|
189
212
|
continue
|
|
190
213
|
}
|
|
191
|
-
out.push({ name, packageRoot, rulesDir, manifest
|
|
214
|
+
out.push({ name, packageRoot, rulesDir, manifest })
|
|
192
215
|
}
|
|
193
216
|
RESOLVE_CACHE.set(cacheKey, out)
|
|
194
217
|
return out
|
|
@@ -207,7 +230,9 @@ export function resolveRulesDirs(projectRoot, config, bundledRulesDir, options =
|
|
|
207
230
|
const plugins = resolvePlugins(projectRoot, config, options)
|
|
208
231
|
return [
|
|
209
232
|
{ name: '@7n/rules', rulesDir: bundledRulesDir, packageRoot: null },
|
|
210
|
-
...plugins
|
|
233
|
+
...plugins
|
|
234
|
+
.filter(p => p.manifest.contributes.rules && existsSync(p.rulesDir))
|
|
235
|
+
.map(p => ({ name: p.name, rulesDir: p.rulesDir, packageRoot: p.packageRoot }))
|
|
211
236
|
]
|
|
212
237
|
}
|
|
213
238
|
|
package/skills/taze/SKILL.md
CHANGED
|
@@ -20,10 +20,17 @@ version: '1.2'
|
|
|
20
20
|
прогін міг зависати без жодної діагностики (один величезний непрозорий хід на
|
|
21
21
|
весь монорепо, без проміжних зупинок для перевірки прогресу). Замість цього `npm/skills/taze/js/orchestrate.mjs`:
|
|
22
22
|
|
|
23
|
-
1. Детерміновано, без LLM, виконує кроки 1-3 нижче для всіх гілок: npm/bun (бекап → `bunx taze -w -r latest` → `bun install` → `n-rules taze diff`); якщо знайдені `Cargo.toml`, Rust (бекап → `cargo upgrade --incompatible allow` → `cargo update` → `collectCargoDiff` з `cargo-diff.mjs`, детермінований cargo-еквівалент `n-rules taze diff` — парсить кожен `Cargo.toml` через `smol-toml`, класифікує major за тим самим правилом caret-семантики, включно з Cargo-скороченими версіями `"1"`/`"0.4"`); якщо знайдений кореневий `pyproject.toml`, Python (бекап → по кожній прямій залежності `uv remove <pkg>` + `uv add <pkg>[extras] --bounds lower` → `collectUvDiff
|
|
23
|
+
1. Детерміновано, без LLM, виконує кроки 1-3 нижче для всіх гілок: npm/bun (бекап → `bunx taze -w -r latest` → `bun install` → `n-rules taze diff`); якщо знайдені `Cargo.toml`, Rust (бекап → `cargo upgrade --incompatible allow` → `cargo update` → `collectCargoDiff` з `cargo-diff.mjs`, детермінований cargo-еквівалент `n-rules taze diff` — парсить кожен `Cargo.toml` через `smol-toml`, класифікує major за тим самим правилом caret-семантики, включно з Cargo-скороченими версіями `"1"`/`"0.4"`); якщо знайдений кореневий `pyproject.toml`, Python (бекап → по кожній прямій залежності `uv remove <pkg>` + `uv add <pkg>[extras] --bounds lower` → `collectUvDiff`, парсить PEP 508/PEP 440 через `smol-toml`, той самий caret-принцип класифікації).
|
|
24
24
|
2. Для кожного **окремого** major-пакета/крейта з diff-у — один ізольований, обмежений виклик обраного раннера (кроки 4-6, лише для цього запису; промпт генерує `buildDependencyPrompt`/`buildCargoDependencyPrompt`/`buildUvDependencyPrompt`, не цей SKILL.md).
|
|
25
25
|
3. Детерміновано прибирає бекапи (крок 7) і компонує звіт (крок 8) з результатів усіх ітерацій.
|
|
26
26
|
|
|
27
|
+
Не-npm екосистеми оркестратор веде через **EcosystemProvider-порт**
|
|
28
|
+
(`@7n/rules/plugin-api`, spec `docs/specs/2026-07-18-lang-plugins-extraction-spec.md`):
|
|
29
|
+
Rust — вбудований first-party провайдер (`rust-provider.mjs`, до фази 2), Python —
|
|
30
|
+
плагін **`@7n/rules-lang-python`** (автодетект за кореневим `pyproject.toml`,
|
|
31
|
+
доставляється автоматично при першому запуску; extension-point `taze`). На
|
|
32
|
+
чисто-JS проєкті без цих сигналів у звіті немає навіть згадки про інші екосистеми.
|
|
33
|
+
|
|
27
34
|
Якщо `Cargo.toml` знайдені, але `cargo-edit` не встановлено — Rust-гілка **не**
|
|
28
35
|
блокує інші гілки: оркестратор пропускає Rust-обробку і в звіті перелічує
|
|
29
36
|
знайдені `Cargo.toml` як такі, що потребують ручного прогону (нижче). Так само,
|
|
@@ -4,9 +4,9 @@ title: npm/skills/taze/js
|
|
|
4
4
|
resource: npm/skills/taze/js/
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
| Файл
|
|
8
|
-
|
|
|
9
|
-
| [cargo-diff.mjs](cargo-diff.md)
|
|
10
|
-
| [diff.mjs](diff.md)
|
|
11
|
-
| [orchestrate.mjs](orchestrate.md)
|
|
12
|
-
| [
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------------- | --------- |
|
|
9
|
+
| [cargo-diff.mjs](cargo-diff.md) | JS Module |
|
|
10
|
+
| [diff.mjs](diff.md) | JS Module |
|
|
11
|
+
| [orchestrate.mjs](orchestrate.md) | JS Module |
|
|
12
|
+
| [rust-provider.mjs](rust-provider.md) | JS Module |
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: orchestrate.mjs
|
|
4
4
|
resource: npm/skills/taze/js/orchestrate.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: a1a1ade0
|
|
7
7
|
model: openai-codex/gpt-5.4-mini
|
|
8
8
|
score: 100
|
|
9
9
|
issues: judge:inaccurate:0.98
|
|
@@ -12,34 +12,29 @@ docgen:
|
|
|
12
12
|
|
|
13
13
|
## Огляд
|
|
14
14
|
|
|
15
|
-
Оркеструє
|
|
15
|
+
Оркеструє taze детерміновано, без LLM для службових кроків: бекап маніфестів → масовий bump → diff-класифікація major vs minor/patch → прибирання бекапів → Markdown-звіт. Для КОЖНОГО окремого major-пакета з diff-у виконує один ізольований, обмежений виклик обраного раннера — лише перевірка сумісності й рефакторинг, не сам bump. npm/bun-гілка вбудована; решта екосистем — `EcosystemProvider`-и (контракт `@7n/rules/plugin-api`): Rust як first-party (`rust-provider.mjs`), інші — з плагінів (`@7n/rules-lang-*`, extension-point `taze`). Файл виконує реальні файлові операції (бекапи) і запускає зовнішні команди (`bunx`, `bun`, `git`, команди провайдерів) — не read-only.
|
|
16
16
|
|
|
17
17
|
## Поведінка
|
|
18
18
|
|
|
19
|
-
- **buildDependencyPrompt**
|
|
19
|
+
- **buildDependencyPrompt** — формує промпт ОДНОГО ітеративного виклику (лише перевірка сумісності й рефакторинг) для одного major-пакета npm/bun.
|
|
20
20
|
- **callRunner** — диспетчер одного ітеративного виклику: `pi` — вбудований pi-агент (текст через `deps.out`), `cursor`/`codex` — napi-міст ACP (`@7n/llm-lib/acp`).
|
|
21
21
|
- **backupWorkspacePackageFiles** / **cleanupBackups** — бекап і прибирання `package.json` кожного воркспейсу.
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **findPyprojectManifest** — перевіряє наявність кореневого `pyproject.toml` (uv-конвенція — один файл, не per-package обхід).
|
|
26
|
-
- **formatReport** — компонує підсумковий Markdown-звіт (minor/patch, major-оновлення по кожній з трьох гілок, загальний обсяг змін) без окремого LLM-виклику.
|
|
27
|
-
- **runTazeOrchestrator** — повний прогін: перевіряє, що `cwd` — ізольований worktree, послідовно обробляє npm-, Rust- (за наявності `Cargo.toml` і `cargo-edit`) і Python-гілку (за наявності `pyproject.toml` і `uv`) — бекап → bump → diff → ізольовані виклики раннера по кожному major-запису → прибирання — і повертає звіт.
|
|
22
|
+
- **loadPluginTazeProviders** — завантажує провайдерів з активних плагінів: `.n-rules.json`/автодетект → `resolvePlugins` (плагін доставляється автоматично при першому запуску) → handler-модулі extension-point `taze` → валідація `assertEcosystemProvider`; битий плагін — warning і пропуск, не провал.
|
|
23
|
+
- **formatReport** — компонує підсумковий Markdown-звіт (npm-гілка + секція на кожну екосистему з manifests; екосистема без manifests — тиша) без окремого LLM-виклику.
|
|
24
|
+
- **runTazeOrchestrator** — повний прогін: перевіряє, що `cwd` — ізольований worktree, виконує npm-гілку, далі кожного провайдера наскрізь (detect → available → backup → bump → diff → ізольовані виклики раннера по major-записах → cleanup) і повертає звіт.
|
|
28
25
|
|
|
29
26
|
## Публічний API
|
|
30
27
|
|
|
31
|
-
- buildDependencyPrompt
|
|
28
|
+
- buildDependencyPrompt — промпт одного npm-major-запису.
|
|
32
29
|
- callRunner — виклик обраного раннера (`pi`/`cursor`/`codex`) з одним промптом.
|
|
33
30
|
- backupWorkspacePackageFiles / cleanupBackups — бекап/прибирання `package.json` воркспейсів.
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- findPyprojectManifest — список (0 або 1 запис) кореневого `pyproject.toml`.
|
|
38
|
-
- formatReport — фінальний Markdown-звіт із результатів усіх трьох гілок.
|
|
39
|
-
- runTazeOrchestrator — повна оркестрація taze для npm/bun + Rust + Python.
|
|
31
|
+
- loadPluginTazeProviders — валідні EcosystemProvider-и з handler-модулів плагінів.
|
|
32
|
+
- formatReport — фінальний Markdown-звіт із npm-результатів і записів екосистем.
|
|
33
|
+
- runTazeOrchestrator — повна оркестрація taze; `deps.ecosystemProviders` повністю замінює список провайдерів (для тестів).
|
|
40
34
|
|
|
41
35
|
## Гарантії поведінки
|
|
42
36
|
|
|
43
37
|
- Виконує файлові операції (копіювання/видалення бекапів) і запускає зовнішні команди — НЕ read-only.
|
|
44
38
|
- Перед будь-якою мутацією перевіряє, що `cwd` — ізольований worktree (`assertRunningInWorktree`), інакше кидає виняток.
|
|
45
|
-
-
|
|
39
|
+
- Виняток усередині одного провайдера (bump/diff/команда) не зупиняє інших — фіксується в `error` запису екосистеми й у звіті; `ok` результату тоді false.
|
|
40
|
+
- Падіння одного пакета в ізольованому виклику раннера не втрачає прогрес по інших записах.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: rust-provider.mjs
|
|
4
|
+
resource: npm/skills/taze/js/rust-provider.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: db99881b
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.99
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Огляд
|
|
17
|
+
|
|
18
|
+
Модуль `rustProvider` об’єднує публічні кроки для роботи з Cargo-залежностями: `findCargoManifests` знаходить `Cargo.toml` у репозиторії, `buildCargoDependencyPrompt` готує запит для major-оновлення Rust-крейта, `backupCargoManifests` створює резервні копії маніфестів, а `cleanupCargoBackups` прибирає їх після завершення.
|
|
19
|
+
|
|
20
|
+
`rustProvider` — вбудований first-party EcosystemProvider Rust/Cargo для taze-оркестратора (контракт `@7n/rules/plugin-api`); до фази 2 живе в ядрі, далі виїде в `@7n/rules-lang-rust`. Модуль виконує реальні файлові операції (бекапи `Cargo.toml`/`Cargo.lock`) і запускає зовнішні команди (`find`, `cargo upgrade`/`cargo update`) — не read-only; сам до мережі не звертається (посилання на crates.io — лише текст промпта для раннера).
|
|
21
|
+
|
|
22
|
+
## Поведінка
|
|
23
|
+
|
|
24
|
+
- `buildCargoDependencyPrompt` — формує текст завдання для перевірки major-оновлення одного Rust-крейта: підказує звірити breaking changes через сторінку на `https://crates.io/crates/`, changelog або releases репозиторію, знайти зачеплене використання в коді й або нічого не змінювати, або виконати сумісний рефакторинг із підсумком у відповіді.
|
|
25
|
+
- `findCargoManifests` — знаходить `Cargo.toml` у репозиторії, свідомо пропускаючи `node_modules`, `.worktrees` і `target`.
|
|
26
|
+
- `backupCargoManifests` — створює резервні копії всіх знайдених `Cargo.toml` і спільного кореневого `Cargo.lock`.
|
|
27
|
+
- `cleanupCargoBackups` — видаляє резервні копії `Cargo.toml` і `Cargo.lock` після завершення роботи.
|
|
28
|
+
- `rustProvider` — описує вбудованого provider для Rust/Cargo в taze: визначає виявлення маніфестів, перевірку доступності через `cargo-edit`, резервне копіювання, оновлення залежностей, побудову prompt і очищення бекапів.
|
|
29
|
+
|
|
30
|
+
## Публічний API
|
|
31
|
+
|
|
32
|
+
- buildCargoDependencyPrompt — Формує prompt для одного LLM-циклу оновлення major-версії одного Rust-крейта; оркестратор окремо робить решту кроків без LLM.
|
|
33
|
+
- findCargoManifests — Підбирає всі `Cargo.toml`, оминаючи `node_modules`, `.worktrees` і `target`.
|
|
34
|
+
- backupCargoManifests — Зберігає копії кожного `Cargo.toml` і спільного кореневого `Cargo.lock`; розраховано на один workspace-lock у корені `cwd`, без підтримки кількох незалежних workspace.
|
|
35
|
+
- cleanupCargoBackups — Видаляє тимчасові бекапи `Cargo.toml` і `Cargo.lock` після завершення роботи.
|
|
36
|
+
- rustProvider — Вбудований Rust/Cargo provider для taze-оркестратора у форматі `@7n/rules/plugin-api`; надалі має перейти в `@7n/rules-lang-rust`.
|
|
37
|
+
|
|
38
|
+
## Гарантії поведінки
|
|
39
|
+
|
|
40
|
+
- Виконує файлові операції (бекапи) і запускає зовнішні команди (`find`, `cargo`) — НЕ read-only.
|
|
41
|
+
- Провал cargo-команди в `bump` кидає помилку з exit-кодом і stderr.
|
|
42
|
+
- Пошук маніфестів свідомо пропускає шляхи: `node_modules`, `.worktrees`, `target`.
|