@7n/rules 1.17.3 → 1.18.1
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 +1 -1
- package/scripts/lib/changed-files.mjs +19 -8
- package/scripts/lib/docs/changed-files.md +4 -3
- package/scripts/lib/docs/resolve-plugins.md +1 -1
- package/scripts/lib/resolve-plugins.mjs +3 -2
- package/skills/taze/SKILL.md +5 -5
- package/skills/taze/js/docs/orchestrate.md +2 -2
- package/skills/taze/js/orchestrate.mjs +3 -4
- package/skills/taze/js/cargo-diff.mjs +0 -117
- package/skills/taze/js/docs/cargo-diff.md +0 -41
- package/skills/taze/js/docs/rust-provider.md +0 -42
- package/skills/taze/js/rust-provider.mjs +0 -162
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.18.1] - 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `resolveChangedBase` стійкий до stale-ref: merge-base рахується для обох кандидатів (`origin/main` і локальна `main`) і береться новіший (descendant) — у git-worktree застрягла локальна `main` більше не дає застарілу базу з фантомними «зміненими» файлами давно влитих PR (T0-фіксер створював фейкові change-фрагменти для незачеплених воркспейсів); фолбек на єдиний доступний ref офлайн/без remote збережено
|
|
8
|
+
|
|
9
|
+
## [1.18.0] - 2026-07-18
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Фаза 2 spec lang-plugins-extraction: Rust/Cargo-провайдер taze виїхав із ядра в окремий плагін `@7n/rules-lang-rust` (автодетект за кореневим Cargo.toml, extension-point `taze`) — сигнатури порту EcosystemProvider не змінились, оркестратор більше не має вбудованих провайдерів (усі екосистеми — з плагінів)
|
|
14
|
+
|
|
3
15
|
## [1.17.3] - 2026-07-18
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/package.json
CHANGED
|
@@ -47,21 +47,32 @@ function dropWorktreeCheckouts(paths) {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Визначає git base для scoped-перевірок без зовнішнього runtime-стану.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Кандидати — `origin/main` і локальна `main`: рахуємо merge-base з HEAD для обох
|
|
51
|
+
* і беремо **новіший** (descendant) з двох. Це захищає від stale-ref з будь-якого
|
|
52
|
+
* боку: у git-worktree локальна `main` закріплена за іншим деревом і часто відстає
|
|
53
|
+
* (застаріла база → фантомні «змінені» файли з давно влитих PR), а без свіжого
|
|
54
|
+
* fetch може відставати вже `origin/main`. Якщо доступний лише один ref (офлайн,
|
|
55
|
+
* без remote) — його merge-base; якщо жодного — null, і caller порівнює лише
|
|
56
|
+
* робоче дерево з HEAD. Повернений sha завжди досяжний (це merge-base існуючого
|
|
57
|
+
* ref), тож fail-closed перевірка в `collectChangedFilesSince` не спрацює хибно.
|
|
58
|
+
* Явний `baseRef` (CI: `--base origin/main` після fetch) вимикає вибір —
|
|
53
59
|
* merge-base рахується лише проти нього.
|
|
54
60
|
* @param {string} [cwd] корінь репо
|
|
55
|
-
* @param {string|null} [baseRef] явний ref бази замість
|
|
61
|
+
* @param {string|null} [baseRef] явний ref бази замість вибору origin/main|main
|
|
56
62
|
* @returns {string|null} merge-base commit або null
|
|
57
63
|
*/
|
|
58
64
|
export function resolveChangedBase(cwd = process.cwd(), baseRef = null) {
|
|
59
|
-
|
|
65
|
+
const mergeBaseWith = ref => {
|
|
60
66
|
const result = spawnSync('git', ['merge-base', 'HEAD', ref], { cwd, encoding: 'utf8' })
|
|
61
|
-
|
|
62
|
-
if (base) return base
|
|
67
|
+
return result.status === 0 && !result.error ? result.stdout.trim() : ''
|
|
63
68
|
}
|
|
64
|
-
return null
|
|
69
|
+
if (baseRef) return mergeBaseWith(baseRef) || null
|
|
70
|
+
const [primary, secondary] = ['origin/main', 'main'].map(ref => mergeBaseWith(ref)).filter(Boolean)
|
|
71
|
+
if (!primary || !secondary || primary === secondary) return primary ?? null
|
|
72
|
+
// Обидва ref-и дали різні merge-base: новіший — той, що є нащадком іншого.
|
|
73
|
+
// Якщо гілки merge-base розійшлися (екзотика), лишаємо пріоритет origin/main.
|
|
74
|
+
const ancestry = spawnSync('git', ['merge-base', '--is-ancestor', primary, secondary], { cwd })
|
|
75
|
+
return ancestry.status === 0 && !ancestry.error ? secondary : primary
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
/**
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: changed-files.mjs
|
|
4
4
|
resource: npm/scripts/lib/changed-files.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 1c7d9027
|
|
7
7
|
score: 100
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -19,8 +19,9 @@ collectChangedFilesSince Збирає змінені та незакомічен
|
|
|
19
19
|
|
|
20
20
|
- collectChangedFiles: Збирає список змінених та невідстежених файлів з робочого дерева відносно робочого дерева.
|
|
21
21
|
- resolveChangedBase: Визначає базовий комміт для обмежених перевірок без використання зовнішнього стану.
|
|
22
|
-
-
|
|
23
|
-
-
|
|
22
|
+
- Кандидати — `origin/main` і локальна `main`: merge-base з HEAD рахується для обох, береться новіший (descendant). Це захищає від stale-ref: у git-worktree локальна `main` часто відстає, без свіжого fetch може відставати `origin/main`.
|
|
23
|
+
- Явний другий аргумент `baseRef` (CI: `--base origin/main`) вимикає вибір — merge-base рахується лише проти нього.
|
|
24
|
+
- Якщо доступний лише один ref (офлайн/без remote) — його merge-base; якщо ref-и відсутні, повертає null, і викликаючий порівнює лише робоче дерево з HEAD.
|
|
24
25
|
- collectChangedFilesSince: Збирає список змінених та невідстежених файлів відносно базового комміту.
|
|
25
26
|
- git diff <base>: Порівнює комміт `base` з поточним робочим деревом (включаючи закомічені та незакомічені модифікації).
|
|
26
27
|
- Якщо `base` відсутній, використовується `collectChangedFiles`.
|
|
@@ -3,7 +3,7 @@ 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: d1bfa8f2
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Резолв плагінів @7n/rules: визначає, які пакети-плагіни активні у проєкті, де їхні `rules/`-каталоги, які capabilities вони надають і які handlers надають.
|
|
@@ -30,7 +30,8 @@ export const KNOWN_CI_PLUGINS = Object.freeze({
|
|
|
30
30
|
|
|
31
31
|
/** Відомі мовні плагіни: файловий сигнал екосистеми в корені репо → npm-пакет. */
|
|
32
32
|
export const KNOWN_LANG_PLUGINS = Object.freeze({
|
|
33
|
-
python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python' }
|
|
33
|
+
python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python' },
|
|
34
|
+
rust: { signal: 'Cargo.toml', pkg: '@7n/rules-lang-rust' }
|
|
34
35
|
})
|
|
35
36
|
|
|
36
37
|
const WORKFLOW_YML_RE = /\.ya?ml$/u
|
|
@@ -96,7 +97,7 @@ function detectCiPlugins(projectRoot) {
|
|
|
96
97
|
/**
|
|
97
98
|
* Автодетект плагінів за станом репозиторію: CI-плагіни (файлові сигнали з
|
|
98
99
|
* fallback на `repository.url`) + мовні плагіни (лише файлові сигнали —
|
|
99
|
-
* маніфест екосистеми в
|
|
100
|
+
* маніфест екосистеми в корені — pyproject.toml, кореневий Cargo.toml; URL-fallback для мов безглуздий).
|
|
100
101
|
* @param {string} projectRoot корінь репозиторію
|
|
101
102
|
* @returns {string[]} npm-імена плагінів
|
|
102
103
|
*/
|
package/skills/taze/SKILL.md
CHANGED
|
@@ -25,11 +25,11 @@ version: '1.2'
|
|
|
25
25
|
3. Детерміновано прибирає бекапи (крок 7) і компонує звіт (крок 8) з результатів усіх ітерацій.
|
|
26
26
|
|
|
27
27
|
Не-npm екосистеми оркестратор веде через **EcosystemProvider-порт**
|
|
28
|
-
(`@7n/rules/plugin-api`, spec `docs/specs/2026-07-18-lang-plugins-extraction-spec.md`)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
(`@7n/rules/plugin-api`, spec `docs/specs/2026-07-18-lang-plugins-extraction-spec.md`)
|
|
29
|
+
— плагіни, що доставляються автоматично при першому запуску за файловим сигналом
|
|
30
|
+
(extension-point `taze`): Rust — **`@7n/rules-lang-rust`** (кореневий `Cargo.toml`),
|
|
31
|
+
Python — **`@7n/rules-lang-python`** (кореневий `pyproject.toml`). На чисто-JS
|
|
32
|
+
проєкті без цих сигналів у звіті немає навіть згадки про інші екосистеми.
|
|
33
33
|
|
|
34
34
|
Якщо `Cargo.toml` знайдені, але `cargo-edit` не встановлено — Rust-гілка **не**
|
|
35
35
|
блокує інші гілки: оркестратор пропускає Rust-обробку і в звіті перелічує
|
|
@@ -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: b675f6e7
|
|
7
7
|
model: openai-codex/gpt-5.4-mini
|
|
8
8
|
score: 100
|
|
9
9
|
issues: judge:inaccurate:0.98
|
|
@@ -12,7 +12,7 @@ docgen:
|
|
|
12
12
|
|
|
13
13
|
## Огляд
|
|
14
14
|
|
|
15
|
-
Оркеструє taze детерміновано, без LLM для службових кроків: бекап маніфестів → масовий bump → diff-класифікація major vs minor/patch → прибирання бекапів → Markdown-звіт. Для КОЖНОГО окремого major-пакета з diff-у виконує один ізольований, обмежений виклик обраного раннера — лише перевірка сумісності й рефакторинг, не сам bump. npm/bun-гілка вбудована; решта екосистем — `EcosystemProvider`-и (контракт `@7n/rules/plugin-api`)
|
|
15
|
+
Оркеструє taze детерміновано, без LLM для службових кроків: бекап маніфестів → масовий bump → diff-класифікація major vs minor/patch → прибирання бекапів → Markdown-звіт. Для КОЖНОГО окремого major-пакета з diff-у виконує один ізольований, обмежений виклик обраного раннера — лише перевірка сумісності й рефакторинг, не сам bump. npm/bun-гілка вбудована; решта екосистем — `EcosystemProvider`-и (контракт `@7n/rules/plugin-api`), завантажені з плагінів (`@7n/rules-lang-rust`, `@7n/rules-lang-python`, …; extension-point `taze`) — вбудованих провайдерів немає. Файл виконує реальні файлові операції (бекапи) і запускає зовнішні команди (`bunx`, `bun`, `git`, команди провайдерів) — не read-only.
|
|
16
16
|
|
|
17
17
|
## Поведінка
|
|
18
18
|
|
|
@@ -10,7 +10,6 @@ import { readNRulesConfigLite } from '../../../scripts/lib/read-n-rules-config-l
|
|
|
10
10
|
import { getHandlers, resolvePlugins } from '../../../scripts/lib/resolve-plugins.mjs'
|
|
11
11
|
import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
|
|
12
12
|
import { collectTazeDiff } from './diff.mjs'
|
|
13
|
-
import rustProvider from './rust-provider.mjs'
|
|
14
13
|
|
|
15
14
|
/** Суфікс бекапу package.json — той самий, що й у `diff.mjs`/кроці 1 SKILL.md. */
|
|
16
15
|
const BACKUP_SUFFIX = '.taze-bak'
|
|
@@ -318,8 +317,8 @@ export function formatReport({ minorPatch, totalChanged, results, ecosystems = [
|
|
|
318
317
|
* прибирання → звіт) без LLM, і по одному ізольованому, обмеженому по
|
|
319
318
|
* обсягу виклику `callRunner` на кожен major-пакет (кроки 4-6 SKILL.md) —
|
|
320
319
|
* замість одного величезного непрозорого ходу на весь монорепо. npm/bun-гілка
|
|
321
|
-
* вбудована; решта екосистем — EcosystemProvider
|
|
322
|
-
*
|
|
320
|
+
* вбудована; решта екосистем — EcosystemProvider-и, завантажені з плагінів
|
|
321
|
+
* (`@7n/rules-lang-*`, extension-point `taze`; фаза 2 spec — Rust теж плагін).
|
|
323
322
|
* Падіння одного пакета/однієї екосистеми не втрачає прогрес по інших.
|
|
324
323
|
* @param {{
|
|
325
324
|
* cwd?: string,
|
|
@@ -371,7 +370,7 @@ export async function runTazeOrchestrator(options = {}) {
|
|
|
371
370
|
log('⏭ npm/bun: кореневого package.json немає — гілка пропущена')
|
|
372
371
|
}
|
|
373
372
|
|
|
374
|
-
const providers = deps.ecosystemProviders ??
|
|
373
|
+
const providers = deps.ecosystemProviders ?? (await loadPluginTazeProviders(cwd, log, deps))
|
|
375
374
|
const ecosystems = []
|
|
376
375
|
for (const provider of providers) {
|
|
377
376
|
ecosystems.push(await runEcosystem(provider, { cwd, runner, log, deps, spawnFn, call }))
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/cargo-diff.md */
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { readFile } from 'node:fs/promises'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { parse as parseToml } from 'smol-toml'
|
|
6
|
-
|
|
7
|
-
import { isBreaking } from './diff.mjs'
|
|
8
|
-
|
|
9
|
-
/** Поля Cargo.toml із залежностями, які порівнюємо (аналог DEP_FIELDS у diff.mjs). */
|
|
10
|
-
const CARGO_DEP_FIELDS = ['dependencies', 'dev-dependencies', 'build-dependencies']
|
|
11
|
-
|
|
12
|
-
/** Дефолтний суфікс бекапу — той самий, що й для package.json (крок 1 SKILL.md, Rust-гілка). */
|
|
13
|
-
const DEFAULT_BACKUP_SUFFIX = '.taze-bak'
|
|
14
|
-
|
|
15
|
-
// 1-3 числові компоненти (`"1"`, `"0.4"`, `"1.2.3"`), опційний `=`/`~`/`^`/`>=`-префікс.
|
|
16
|
-
//Cargo трактує відсутні компоненти як 0 і для матчингу, і для caret-сумісності —
|
|
17
|
-
// той самий "найлівіша ненульова компонента" принцип, що й у isBreaking з diff.mjs.
|
|
18
|
-
const CARGO_VERSION_RE = /^[\s=~^><]*(\d+)(?:\.(\d+))?(?:\.(\d+))?/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Парсить Cargo-версійний specifier (1-3 компоненти, відсутні → 0).
|
|
22
|
-
* @param {string} spec версійний specifier із Cargo.toml (`"1"`, `"0.4.2"`, `"=1.2.3"`)
|
|
23
|
-
* @returns {{major:number, minor:number, patch:number}|null} ядро або null для не-semver
|
|
24
|
-
*/
|
|
25
|
-
export function parseCargoVersion(spec) {
|
|
26
|
-
if (typeof spec !== 'string') return null
|
|
27
|
-
const m = CARGO_VERSION_RE.exec(spec)
|
|
28
|
-
if (!m) return null
|
|
29
|
-
return { major: Number(m[1]), minor: Number(m[2] ?? 0), patch: Number(m[3] ?? 0) }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Дістає версійний specifier одного запису залежності Cargo.toml — рядок
|
|
34
|
-
* напряму (`tokio = "1"`), або поле `version` inline-таблиці
|
|
35
|
-
* (`serde = { version = "1", features = [...] }`).
|
|
36
|
-
* @param {unknown} value значення запису залежності
|
|
37
|
-
* @returns {string|null} specifier, або null для path/git-залежності (без номера версії)
|
|
38
|
-
*/
|
|
39
|
-
export function extractCargoVersionSpec(value) {
|
|
40
|
-
if (typeof value === 'string') return value
|
|
41
|
-
if (value && typeof value === 'object' && !Array.isArray(value) && typeof value.version === 'string') {
|
|
42
|
-
return value.version
|
|
43
|
-
}
|
|
44
|
-
return null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Порівнює два розпарсені Cargo.toml і повертає зміни залежностей —
|
|
49
|
-
* той самий контракт, що й `diffPackageJson` у diff.mjs, лише для Cargo.
|
|
50
|
-
* @param {object} oldManifest розпарсений старий Cargo.toml (бекап)
|
|
51
|
-
* @param {object} newManifest розпарсений новий Cargo.toml
|
|
52
|
-
* @param {string} manifest відносний шлях Cargo.toml (мітка джерела запису)
|
|
53
|
-
* @returns {{major: Array<{manifest:string, pkg:string, from:string, to:string}>, minorPatch:number}} зміни
|
|
54
|
-
*/
|
|
55
|
-
export function diffCargoToml(oldManifest, newManifest, manifest) {
|
|
56
|
-
const major = []
|
|
57
|
-
let minorPatch = 0
|
|
58
|
-
for (const field of CARGO_DEP_FIELDS) {
|
|
59
|
-
const oldDeps = oldManifest?.[field]
|
|
60
|
-
const newDeps = newManifest?.[field]
|
|
61
|
-
if (!oldDeps || !newDeps) continue
|
|
62
|
-
for (const [crate, oldValue] of Object.entries(oldDeps)) {
|
|
63
|
-
const newValue = newDeps[crate]
|
|
64
|
-
if (newValue === undefined) continue
|
|
65
|
-
const from = extractCargoVersionSpec(oldValue)
|
|
66
|
-
const to = extractCargoVersionSpec(newValue)
|
|
67
|
-
if (from === null || to === null || from === to) continue
|
|
68
|
-
const fromV = parseCargoVersion(from)
|
|
69
|
-
const toV = parseCargoVersion(to)
|
|
70
|
-
if (fromV && toV && isBreaking(fromV, toV)) {
|
|
71
|
-
major.push({ manifest, pkg: crate, from, to })
|
|
72
|
-
} else {
|
|
73
|
-
minorPatch += 1
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { major, minorPatch }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Читає й парсить TOML-файл, або повертає null, якщо файл відсутній/невалідний.
|
|
82
|
-
* @param {string} path абсолютний шлях
|
|
83
|
-
* @returns {Promise<object|null>} розпарсений обʼєкт або null
|
|
84
|
-
*/
|
|
85
|
-
async function readTomlOrNull(path) {
|
|
86
|
-
if (!existsSync(path)) return null
|
|
87
|
-
try {
|
|
88
|
-
return parseToml(await readFile(path, 'utf8'))
|
|
89
|
-
} catch {
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Збирає diff по всіх Cargo.toml монорепо: для кожного шляху зі списку
|
|
96
|
-
* порівнює `<manifest>` з `<manifest><backupSuffix>` — той самий контракт,
|
|
97
|
-
* що й `collectTazeDiff` для package.json.
|
|
98
|
-
* @param {string} cwd корінь репозиторію
|
|
99
|
-
* @param {string[]} manifestPaths відносні шляхи Cargo.toml (з `findCargoManifests`)
|
|
100
|
-
* @param {string} [backupSuffix] суфікс бекап-файлу
|
|
101
|
-
* @returns {Promise<{major: Array<{manifest:string, pkg:string, from:string, to:string}>, minorPatch:number, totalChanged:number, comparedManifests:number}>} агрегований diff
|
|
102
|
-
*/
|
|
103
|
-
export async function collectCargoDiff(cwd, manifestPaths, backupSuffix = DEFAULT_BACKUP_SUFFIX) {
|
|
104
|
-
const major = []
|
|
105
|
-
let minorPatch = 0
|
|
106
|
-
let comparedManifests = 0
|
|
107
|
-
for (const manifest of manifestPaths) {
|
|
108
|
-
const oldManifest = await readTomlOrNull(join(cwd, `${manifest}${backupSuffix}`))
|
|
109
|
-
const newManifest = await readTomlOrNull(join(cwd, manifest))
|
|
110
|
-
if (!oldManifest || !newManifest) continue
|
|
111
|
-
comparedManifests += 1
|
|
112
|
-
const res = diffCargoToml(oldManifest, newManifest, manifest)
|
|
113
|
-
major.push(...res.major)
|
|
114
|
-
minorPatch += res.minorPatch
|
|
115
|
-
}
|
|
116
|
-
return { major, minorPatch, totalChanged: major.length + minorPatch, comparedManifests }
|
|
117
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: cargo-diff.mjs
|
|
4
|
-
resource: npm/skills/taze/js/cargo-diff.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: afe20497
|
|
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
|
-
`package.json`
|
|
17
|
-
|
|
18
|
-
Файл читає версії з `Cargo.toml`, виділяє специфікації залежностей, порівнює дві версії маніфесту та збирає diff по змінах версій. Для монорепо проходить по списку `Cargo.toml` і агрегує результати для всіх знайдених маніфестів. Усі помилки перехоплює fail-safe: не кидає винятків назовні й за окремих збоїв повертає порожнє значення замість помилки.
|
|
19
|
-
|
|
20
|
-
## Поведінка
|
|
21
|
-
|
|
22
|
-
- parseCargoVersion — виділяє версійну частину Cargo-специфікатора й повертає її як нормалізоване числове ядро; для не-версійних значень повертає null.
|
|
23
|
-
- extractCargoVersionSpec — дістає версійний specifier із запису залежності Cargo.toml; для path/git-залежностей без версії повертає null.
|
|
24
|
-
- diffCargoToml — порівнює два розпарсені Cargo.toml і визначає зміни залежностей, розділяючи їх на major та minor/patch.
|
|
25
|
-
- collectCargoDiff — проходить по списку Cargo.toml у монорепо, зіставляє кожен файл із його backup-файлом і збирає агрегований diff; якщо один із файлів відсутній або невалідний, пропускає його без падіння.
|
|
26
|
-
|
|
27
|
-
## Публічний API
|
|
28
|
-
|
|
29
|
-
- Конфіги, на які спирається код: `package.json` — джерело списку залежностей і меж для порівняння змін.
|
|
30
|
-
- `parseCargoVersion` — розбирає Cargo-версійний specifier і заповнює відсутні частини нулями.
|
|
31
|
-
- `extractCargoVersionSpec` — дістає версію залежності з запису `Cargo.toml`, як з короткого рядка, так і з inline-таблиці.
|
|
32
|
-
- `diffCargoToml` — порівнює два розібрані `Cargo.toml` і повертає зміни по залежностях.
|
|
33
|
-
- `collectCargoDiff` — збирає diff для всіх `Cargo.toml` у монорепо, зіставляючи кожен маніфест із його backup-версією.
|
|
34
|
-
|
|
35
|
-
Changelog: не перевіряв — змін у файлах не вносив.
|
|
36
|
-
|
|
37
|
-
## Гарантії поведінки
|
|
38
|
-
|
|
39
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
40
|
-
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
41
|
-
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: rust-provider.mjs
|
|
4
|
-
resource: npm/skills/taze/js/rust-provider.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 5f9de45d
|
|
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`.
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/rust-provider.md */
|
|
2
|
-
import { spawnSync } from 'node:child_process'
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
4
|
-
import { copyFile, rm } from 'node:fs/promises'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
|
|
7
|
-
import { collectCargoDiff } from './cargo-diff.mjs'
|
|
8
|
-
|
|
9
|
-
/** Суфікс бекапу — той самий, що й для package.json (крок 1 SKILL.md, Rust-гілка). */
|
|
10
|
-
const BACKUP_SUFFIX = '.taze-bak'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Промпт ОДНОГО ітеративного виклику для Rust-крейта (кроки 4-6 SKILL.md,
|
|
14
|
-
* Rust-гілка) для ОДНОГО major-крейта. Кроки 1-3/7/8 виконує оркестратор
|
|
15
|
-
* детерміновано, без LLM.
|
|
16
|
-
* @param {{manifest: string, pkg: string, from: string, to: string}} entry запис major-diff (з `collectCargoDiff`)
|
|
17
|
-
* @returns {string} готовий промпт
|
|
18
|
-
*/
|
|
19
|
-
export function buildCargoDependencyPrompt({ manifest, pkg, from, to }) {
|
|
20
|
-
return [
|
|
21
|
-
'# Major-оновлення одного Rust-крейта: перевірка сумісності й рефакторинг',
|
|
22
|
-
'',
|
|
23
|
-
`Крейт \`${pkg}\` у \`${manifest}\`: **${from} → ${to}** — вже застосовано в Cargo.toml/Cargo.lock (кроки 1-3 виконано детерміновано, без тебе). Твоя задача — лише breaking-changes-перевірка й, за потреби, рефакторинг.`,
|
|
24
|
-
'',
|
|
25
|
-
'## Кроки',
|
|
26
|
-
`1. Зібрати breaking changes цього оновлення: адреса репозиторію з поля \`repository\`/\`documentation\` крейта на crates.io (https://crates.io/crates/${pkg}) — CHANGELOG.md репозиторію чи GitHub Releases. Якщо немає — різниця по публічному API (\`pub fn\`/\`pub struct\`/\`pub trait\`) між закешованою старою версією (\`~/.cargo/registry/src/*/${pkg}-<стара-версія>/\`) і новою.`,
|
|
27
|
-
`2. Знайти використання зачепленого API в коді проєкту (\`rg -n --type rust\` по use-шляхах/викликах \`${pkg}\`).`,
|
|
28
|
-
'3. Сумісно — нічого не робити. Несумісно — застосувати міграцію (перейменувати use-шлях, оновити сигнатуру виклику, замінити видалений макрос еквівалентом).',
|
|
29
|
-
'4. Якщо були правки — запусти `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features -- -D warnings`, `cargo test`.',
|
|
30
|
-
'5. Нетривіальна/неоднозначна міграція — не вгадуй, залиш TODO-коментар із посиланням на CHANGELOG.',
|
|
31
|
-
'',
|
|
32
|
-
'У відповіді одним абзацом підсумуй: сумісно / зрефакторено (які файли) / TODO (чому).'
|
|
33
|
-
].join('\n')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Знаходить Cargo.toml поза node_modules/.worktrees/target (крок 0.2 SKILL.md).
|
|
38
|
-
* @param {string} cwd корінь репо
|
|
39
|
-
* @param {{ spawnFn?: (cmd: string, args: string[], opts?: object) => { status: number|null, stdout: string, stderr: string } }} deps інжект зі spawnSync-сумісним викликом
|
|
40
|
-
* @returns {string[]} відносні шляхи знайдених Cargo.toml
|
|
41
|
-
*/
|
|
42
|
-
export function findCargoManifests(cwd, deps = {}) {
|
|
43
|
-
const spawnFn = deps.spawnFn ?? spawnSync
|
|
44
|
-
const result = spawnFn(
|
|
45
|
-
'find',
|
|
46
|
-
[
|
|
47
|
-
'.',
|
|
48
|
-
'-name',
|
|
49
|
-
'Cargo.toml',
|
|
50
|
-
'-not',
|
|
51
|
-
'-path',
|
|
52
|
-
'*/node_modules/*',
|
|
53
|
-
'-not',
|
|
54
|
-
'-path',
|
|
55
|
-
'*/.worktrees/*',
|
|
56
|
-
'-not',
|
|
57
|
-
'-path',
|
|
58
|
-
'*/target/*'
|
|
59
|
-
],
|
|
60
|
-
{ cwd, encoding: 'utf8' }
|
|
61
|
-
)
|
|
62
|
-
return (result.stdout ?? '')
|
|
63
|
-
.split('\n')
|
|
64
|
-
.map(line => line.trim())
|
|
65
|
-
.filter(Boolean)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Чи встановлений cargo-edit (дає `cargo upgrade`) — без нього неможливо
|
|
70
|
-
* детерміновано перетнути major-межу Rust-залежностей (голий `cargo update`
|
|
71
|
-
* піднімає лише semver-сумісні версії; SKILL.md §Передумови).
|
|
72
|
-
* @param {import('../../../scripts/lib/plugin-api.mjs').SpawnFn} spawnFn spawnSync-сумісний виклик
|
|
73
|
-
* @returns {boolean} true — `cargo upgrade` доступна
|
|
74
|
-
*/
|
|
75
|
-
function hasCargoEdit(spawnFn) {
|
|
76
|
-
return spawnFn('cargo', ['upgrade', '--version'], { encoding: 'utf8' }).status === 0
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Бекапить кожен Cargo.toml + спільний кореневий Cargo.lock (крок 1 SKILL.md,
|
|
81
|
-
* Rust-гілка). v1: один спільний workspace/Cargo.lock у корені `cwd` —
|
|
82
|
-
* поточна реальна топологія репо; кілька незалежних Cargo-workspace поки не
|
|
83
|
-
* підтримується.
|
|
84
|
-
* @param {string} cwd корінь репо
|
|
85
|
-
* @param {string[]} manifestPaths відносні шляхи Cargo.toml (з `findCargoManifests`)
|
|
86
|
-
* @param {{ copyFile?: (src: string, dest: string) => Promise<void> }} [deps] інжект
|
|
87
|
-
* @returns {Promise<void>}
|
|
88
|
-
*/
|
|
89
|
-
export async function backupCargoManifests(cwd, manifestPaths, deps = {}) {
|
|
90
|
-
const copy = deps.copyFile ?? copyFile
|
|
91
|
-
for (const manifest of manifestPaths) {
|
|
92
|
-
const manifestPath = join(cwd, manifest)
|
|
93
|
-
if (existsSync(manifestPath)) await copy(manifestPath, `${manifestPath}${BACKUP_SUFFIX}`)
|
|
94
|
-
}
|
|
95
|
-
const lockPath = join(cwd, 'Cargo.lock')
|
|
96
|
-
if (existsSync(lockPath)) await copy(lockPath, `${lockPath}${BACKUP_SUFFIX}`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Прибирає бекапи Cargo.toml/Cargo.lock після завершення (крок 7 SKILL.md,
|
|
101
|
-
* Rust-гілка).
|
|
102
|
-
* @param {string} cwd корінь репо
|
|
103
|
-
* @param {string[]} manifestPaths відносні шляхи Cargo.toml (з `findCargoManifests`)
|
|
104
|
-
* @param {{ rm?: (path: string, opts?: object) => Promise<void> }} [deps] інжект
|
|
105
|
-
* @returns {Promise<void>}
|
|
106
|
-
*/
|
|
107
|
-
export async function cleanupCargoBackups(cwd, manifestPaths, deps = {}) {
|
|
108
|
-
const remove = deps.rm ?? rm
|
|
109
|
-
for (const manifest of manifestPaths) {
|
|
110
|
-
await remove(join(cwd, `${manifest}${BACKUP_SUFFIX}`), { force: true })
|
|
111
|
-
}
|
|
112
|
-
await remove(join(cwd, `Cargo.lock${BACKUP_SUFFIX}`), { force: true })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Виконує cargo-команду, кидає з exit-кодом+stderr при провалі.
|
|
117
|
-
* @param {string[]} args аргументи cargo
|
|
118
|
-
* @param {string} cwd робочий каталог
|
|
119
|
-
* @param {import('../../../scripts/lib/plugin-api.mjs').SpawnFn} spawnFn spawnSync-сумісний виклик
|
|
120
|
-
* @returns {void}
|
|
121
|
-
*/
|
|
122
|
-
function runCargo(args, cwd, spawnFn) {
|
|
123
|
-
const result = spawnFn('cargo', args, { cwd, encoding: 'utf8' })
|
|
124
|
-
if (result.status !== 0) {
|
|
125
|
-
throw new Error(`cargo ${args.join(' ')} → exit ${result.status}: ${result.stderr || result.stdout}`)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Вбудований (first-party) EcosystemProvider Rust/Cargo для taze-оркестратора —
|
|
131
|
-
* форма контракту `@7n/rules/plugin-api`. Лишається в ядрі до фази 2
|
|
132
|
-
* (spec 2026-07-18-lang-plugins-extraction), далі виїде в `@7n/rules-lang-rust`.
|
|
133
|
-
* @type {import('../../../scripts/lib/plugin-api.mjs').EcosystemProvider}
|
|
134
|
-
*/
|
|
135
|
-
const rustProvider = {
|
|
136
|
-
id: 'rust-cargo',
|
|
137
|
-
title: 'Rust-крейти',
|
|
138
|
-
manifestNoun: 'Cargo.toml',
|
|
139
|
-
skillSection: 'Rust-гілкою SKILL.md',
|
|
140
|
-
detect: (cwd, deps) => findCargoManifests(cwd, deps),
|
|
141
|
-
available: spawnFn =>
|
|
142
|
-
hasCargoEdit(spawnFn)
|
|
143
|
-
? { ok: true, reason: null }
|
|
144
|
-
: {
|
|
145
|
-
ok: false,
|
|
146
|
-
reason:
|
|
147
|
-
'cargo-edit не встановлено (`cargo install cargo-edit`) — cargo update без нього недетермінований для major'
|
|
148
|
-
},
|
|
149
|
-
backup: backupCargoManifests,
|
|
150
|
-
bump: (cwd, manifests, { spawnFn, log }) => {
|
|
151
|
-
log('⬆️ cargo upgrade --incompatible allow...')
|
|
152
|
-
runCargo(['upgrade', '--incompatible', 'allow'], cwd, spawnFn)
|
|
153
|
-
log('🔄 cargo update...')
|
|
154
|
-
runCargo(['update'], cwd, spawnFn)
|
|
155
|
-
return Promise.resolve()
|
|
156
|
-
},
|
|
157
|
-
diff: (cwd, manifests) => collectCargoDiff(cwd, manifests),
|
|
158
|
-
promptFor: buildCargoDependencyPrompt,
|
|
159
|
-
cleanup: cleanupCargoBackups
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export default rustProvider
|