@7n/rules 1.25.1 → 1.26.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.26.0] - 2026-07-19
4
+
5
+ ### Changed
6
+
7
+ - Фаза 5a spec lang-plugins-extraction: ядро — двигун без мовної специфіки. npm/bun-гілка taze виїхала в плагін `@7n/rules-lang-js` (EcosystemProvider; бекап воркспейсів, `bunx taze -w -r latest` + `bun install`, collectTazeDiff, SKILL-фрагмент); semver-примітиви (parseVersion/isBreaking) тепер визначені в `@7n/rules/plugin-api` (було — реекспорт із diff.mjs). Оркестратор — чистий цикл провайдерів (без вбудованих гілок), звіт — рівноправні секції екосистем; CLI `n-rules taze diff` резолвиться через handler lang-js. Автодетект — кореневий package.json → lang-js
8
+
3
9
  ## [1.25.1] - 2026-07-19
4
10
 
5
11
  ### Fixed
package/bin/n-rules.js CHANGED
@@ -1883,7 +1883,22 @@ try {
1883
1883
  // n-rules taze diff — read-only semver-diff package.json ↔ package.json.taze-bak
1884
1884
  // (root + воркспейси) для скілу n-taze: скрипт класифікує major-оновлення,
1885
1885
  // агент отримує готовий список замість ручного порівняння бекапів.
1886
- const { runTazeCli } = await import('../skills/taze/js/diff.mjs')
1886
+ // Живе у плагіні @7n/rules-lang-js (фаза 5a spec lang-plugins-extraction)
1887
+ // резолвимо його taze-handler і беремо named-експорт runTazeCli.
1888
+ const { getHandlers } = await import('../scripts/lib/resolve-plugins.mjs')
1889
+ const { readNRulesConfigLite } = await import('../scripts/lib/read-n-rules-config-lite.mjs')
1890
+ const config = await readNRulesConfigLite(cwd())
1891
+ const handler = getHandlers(cwd(), config, 'taze').find(h => h.pluginName === '@7n/rules-lang-js')
1892
+ if (!handler) {
1893
+ console.error(
1894
+ '❌ taze diff потребує плагін @7n/rules-lang-js (npm/bun-гілка) — запусти npx @7n/rules для авто-встановлення'
1895
+ )
1896
+ process.exitCode = 1
1897
+ break
1898
+ }
1899
+ const { pathToFileURL } = await import('node:url')
1900
+ // eslint-disable-next-line no-unsanitized/method
1901
+ const { runTazeCli } = await import(pathToFileURL(handler.modulePath).href)
1887
1902
  process.exitCode = await runTazeCli(args)
1888
1903
 
1889
1904
  break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.25.1",
3
+ "version": "1.26.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: plugin-api.mjs
4
4
  resource: npm/scripts/lib/plugin-api.mjs
5
5
  docgen:
6
- crc: dc4f4a12
6
+ crc: c614a880
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- tier: local-min-retry
9
8
  score: 100
10
9
  issues: judge:inaccurate:0.98
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -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: 0d197e39
6
+ crc: fb58860d
7
7
  ---
8
8
 
9
9
  Резолв плагінів @7n/rules: визначає, які пакети-плагіни активні у проєкті, де їхні `rules/`-каталоги, які capabilities вони надають і які handlers надають.
@@ -7,11 +7,40 @@
7
7
  * модуль-обробник експортує обʼєкт провайдера як `default`. Наступні порти
8
8
  * (doc-files, lint) додаються окремими фазами — не проєктуються наперед.
9
9
  *
10
- * Semver-утиліти ядра реекспортуються звідси, щоб плагіни класифікували
11
- * major/minor за тим самим правилом caret-семантики, що й ядро, без
12
- * дублювання коду і без імпорту внутрішніх шляхів `@7n/rules`.
10
+ * Semver-утиліти (caret-класифікація major/minor) живуть саме тут — єдине
11
+ * джерело правила для всіх мовних плагінів, без імпорту внутрішніх шляхів
12
+ * `@7n/rules` і без циклу plugin-api плагін.
13
13
  */
14
- export { isBreaking, parseVersion } from '../../skills/taze/js/diff.mjs'
14
+ // Заякорено на початок (після можливих range-операторів `^~>=<`, пробілів, `v`),
15
+ // щоб НЕ ловити версію всередині protocol-specifier-ів (`workspace:1.0.0`, `npm:x@1.2.3`).
16
+ const SEMVER_RE = /^[\s~^>=<v]*(\d+)\.(\d+)\.(\d+)/
17
+
18
+ /**
19
+ * Парсить semver-ядро зі specifier-а (ігнорує range-префікси `^`/`~`/`>=` тощо).
20
+ * @param {string} spec версійний specifier із package.json
21
+ * @returns {{major:number, minor:number, patch:number}|null} ядро або null для не-semver (`workspace:*`, git-url, `*`)
22
+ */
23
+ export function parseVersion(spec) {
24
+ if (typeof spec !== 'string') return null
25
+ const m = SEMVER_RE.exec(spec)
26
+ if (!m) return null
27
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) }
28
+ }
29
+
30
+ /**
31
+ * Чи є перехід `from → to` breaking за caret-семантикою (змінилась найлівіша
32
+ * ненульова компонента).
33
+ * @param {{major:number,minor:number,patch:number}} from стара версія
34
+ * @param {{major:number,minor:number,patch:number}} to нова версія
35
+ * @returns {boolean} true — major/breaking
36
+ */
37
+ export function isBreaking(from, to) {
38
+ if (from.major !== to.major) return true
39
+ if (from.major > 0) return false
40
+ if (from.minor !== to.minor) return true
41
+ if (from.minor > 0) return false
42
+ return from.patch !== to.patch
43
+ }
15
44
 
16
45
  /** Версія контракту plugin-api: плагін декларує `requiresPluginApi`, несумісність → skip, не креш. */
17
46
  export const PLUGIN_API_VERSION = 1
@@ -31,11 +31,12 @@ export const KNOWN_CI_PLUGINS = Object.freeze({
31
31
  /**
32
32
  * Відомі мовні плагіни: файловий сигнал екосистеми → npm-пакет. `maxDepth` —
33
33
  * до якої глибини шукати сигнал: python — лише корінь (uv-провайдер v1
34
- * обробляє тільки кореневий pyproject.toml); rust — до 3 рівнів, бо в
34
+ * обробляє тільки кореневий pyproject.toml; js — кореневий package.json); rust — до 3 рівнів, бо в
35
35
  * монорепо Cargo.toml часто вкладений (Tauri `app/src-tauri/Cargo.toml`),
36
36
  * а провайдер обробляє всі знайдені маніфести.
37
37
  */
38
38
  export const KNOWN_LANG_PLUGINS = Object.freeze({
39
+ js: { signal: 'package.json', pkg: '@7n/rules-lang-js', maxDepth: 0 },
39
40
  python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python', maxDepth: 0 },
40
41
  rust: { signal: 'Cargo.toml', pkg: '@7n/rules-lang-rust', maxDepth: 3 }
41
42
  })
@@ -1,144 +1,96 @@
1
1
  ---
2
2
  name: n-taze
3
3
  description: >-
4
- Оновлення версій модулів проекту (bun/npm вбудовано; інші екосистеми — Rust,
4
+ Оновлення версій модулів проекту до останніх (усі екосистеми — JS/npm, Rust,
5
5
  Python тощо — через активні lang-плагіни) з аналізом major-змін і
6
6
  автоматичним рефакторингом несумісного коду
7
- version: '1.2'
7
+ version: '2.0'
8
8
  ---
9
9
 
10
10
  # n-taze — Оновлення версій проекту
11
11
 
12
12
  ## Мета
13
13
 
14
- Оновити всі модулі проекту (npm/bun-залежності вбудовано; інші екосистеми — за активними lang-плагінами, їхні гілки описані у фрагментах наприкінці цього файлу) до останніх версій, виявити major-оновлення, перевірити сумісність змін з кодом проекту і за потреби зрефакторити несумісні місця.
14
+ Оновити всі модулі проекту до останніх версій, виявити major-оновлення, перевірити сумісність змін з кодом проекту і за потреби зрефакторити несумісні місця. Кожна екосистема (JS/npm/bun, Rust/Cargo, Python/uv, …) — гілка відповідного lang-плагіна; конкретні команди гілок описані у фрагментах плагінів наприкінці цього файлу.
15
15
 
16
- ## Оркестрація (npm/bun + екосистемні гілки плагінів) — не одним промптом
16
+ ## Оркестрація — не одним промптом
17
17
 
18
18
  `npx @7n/rules skill pi|cursor|codex taze` **не** передає цей файл одним суцільним
19
19
  промптом в один агентський хід — так робив старий дизайн, і саме тому реальний
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`), а кожну екосистемну гілку — через її EcosystemProvider (detect → backup → bump → diff; деталі команд — у фрагменті відповідного плагіна наприкінці файлу).
24
- 2. Для кожного **окремого** major-запису з diff-у — один ізольований, обмежений виклик обраного раннера (кроки 4-6, лише для цього запису; промпт генерує `buildDependencyPrompt` ядра або `promptFor` провайдера, не цей SKILL.md).
23
+ 1. Детерміновано, без LLM, виконує кроки 1-3 нижче для кожної екосистемної гілки через її EcosystemProvider (detect → backup → bump → diff; конкретні команди — у фрагменті відповідного плагіна наприкінці файлу).
24
+ 2. Для кожного **окремого** major-запису з diff-у — один ізольований, обмежений виклик обраного раннера (кроки 4-6, лише для цього запису; промпт генерує `promptFor` провайдера, не цей SKILL.md).
25
25
  3. Детерміновано прибирає бекапи (крок 7) і компонує звіт (крок 8) з результатів усіх ітерацій.
26
26
 
27
- Не-npm екосистеми оркестратор веде через **EcosystemProvider-порт**
27
+ Усі екосистеми оркестратор веде через **EcosystemProvider-порт**
28
28
  (`@7n/rules/plugin-api`, spec `docs/specs/2026-07-18-lang-plugins-extraction-spec.md`)
29
29
  — lang-плагіни доставляються автоматично при першому запуску за файловим
30
- сигналом (extension-point `taze`) і додають свої гілки як фрагменти цього
31
- SKILL.md (блок наприкінці файлу). На чисто-JS проєкті без таких сигналів у
32
- звіті немає навіть згадки про інші екосистеми. Якщо тулчейн гілки не
33
- встановлено вона пропускається з причиною у звіті, не блокуючи інші.
30
+ сигналом (extension-point `taze`; JS/npm `@7n/rules-lang-js` за кореневим
31
+ `package.json`) і додають свої гілки як фрагменти цього SKILL.md (блок
32
+ наприкінці файлу). Ядро двигун без мовної специфіки: без сигналу екосистеми
33
+ у звіті немає навіть згадки про неї. Якщо тулчейн гілки не встановлено — вона
34
+ пропускається з причиною у звіті, не блокуючи інші.
34
35
 
35
36
  Переваги: падіння/timeout на одному пакеті/крейті не втрачає прогрес по інших;
36
37
  кожен виклик успадковує власний timeout раннера (короткий, бо скоуп малий —
37
38
  один запис, не весь монорепо); видно прогрес по записах, а не чорну скриньку.
38
39
 
39
- Кроки 1-8 нижче лишаються джерелом правди щодо ЗМІСТУ роботи npm/bun-гілки
40
- скелетом для екосистемних)оркестратор їх виконує програмно (1-3/7/8)
41
- або як промпт на один запис (4-6). Мовні відповідники кроків у фрагментах
42
- плагінів наприкінці файлу.
40
+ Кроки 1-8 нижче **скелет** роботи будь-якої гілки (що означає кожен крок);
41
+ конкретні команди кожної екосистемиу фрагменті її плагіна. Оркестратор
42
+ виконує кроки 1-3/7/8 програмно, 4-6 як промпт на один запис.
43
43
 
44
44
  ## Передумови
45
45
 
46
- - Чисте робоче дерево (`git status` без незакомічених змін у `package.json` / `bun.lock` / `node_modules`) — інакше різницю не відрізнити від оновлення.
47
- - Встановлений `bun` і доступний `bunx`.
48
- - Запуск з кореня проекту (де лежить `package.json` / `bun.lock`).
49
- - Передумови екосистемних гілок (інструменти на кшталт cargo-edit чи uv) — у фрагментах плагінів наприкінці файлу; відсутній тулчейн не блокує інші гілки.
46
+ - Чисте робоче дерево (`git status` без незакомічених змін у маніфестах/lock-файлах залежностей) — інакше різницю не відрізнити від оновлення.
47
+ - Запуск з кореня проекту.
48
+ - Передумови конкретних гілок (bun/bunx, cargo-edit, uv тощо) — у фрагментах плагінів наприкінці файлу; відсутній тулчейн не блокує інші гілки.
50
49
 
51
- ## Workflow
50
+ ## Workflow (скелет — конкретика у фрагментах плагінів)
52
51
 
53
52
  ### 1. Зафіксувати стартовий стан
54
53
 
55
- Перед оновленням зберегти список поточних версій, щоб потім обчислити, які модулі стрибнули через major:
56
-
57
- ```bash
58
- cp package.json package.json.taze-bak
59
- cp bun.lock bun.lock.taze-bak
60
- ```
61
-
62
- (У monorepo — також усі `*/package.json` воркспейсів. Файли тимчасові, видалити в кінці.)
54
+ Перед оновленням зберегти копії маніфестів залежностей (`<manifest>.taze-bak`) — щоб потім детерміновано обчислити, які модулі стрибнули через major. Файли тимчасові, видаляються в кінці.
63
55
 
64
56
  ### 2. Запустити оновлення
65
57
 
66
- ```bash
67
- bunx taze -w -r latest
68
- bun install
69
- ```
70
-
71
- - `-w` — записати нові версії у `package.json`.
72
- - `-r` — рекурсивно по всіх воркспейсах.
73
- - `latest` — піднімати навіть major.
58
+ Командою екосистеми підняти всі залежності до останніх версій, **включно з major**, і синхронізувати lock-файл.
74
59
 
75
60
  ### 3. Виявити major-оновлення
76
61
 
77
- > **Не порівнюй `package.json` вручну.** Класифікацію semver несе CLIдетерміновано, по всіх воркспейсах.
78
-
79
- ```bash
80
- n-rules taze diff
81
- ```
62
+ > **Не порівнюй маніфести вручну.** Класифікацію semver несе детермінований diff гілки (порівняння з `.taze-bak`-бекапом за caret-правилом: major = змінилась найлівіша ненульова компонента `1.x→2.x`, `0.4.x→0.5.x`, `0.0.3→0.0.4`).
82
63
 
83
- Друкує компактний JSON: `{ "major": [{workspace, pkg, from, to}], "minorPatch": <N>, "totalChanged": <N> }`. `major` список залежностей, у яких змінилась найлівіша ненульова компонента semver (`1.x→2.x`, `0.4.x→0.5.x`, `0.0.3→0.0.4`); саме він іде в кроки 4–6. `minorPatch` лічба сумісних (для звіту в кроці 8).
84
-
85
- Покриває **прямі** залежності з `package.json` (root + воркспейси). Транзитивні major-стрибки (`bun.lock`) — за потреби переглянь окремо; основний ризик breaking-змін — у прямих.
64
+ Major-записи йдуть у кроки 4–6; лічба сумісних (minor/patch) — у звіт кроку 8. Покриваються **прямі** залежності; транзитивніза потреби окремо.
86
65
 
87
66
  ### 4. Зібрати breaking changes по кожному major-оновленню
88
67
 
89
- Для кожного модуля зі списку зібрати фактичні відмінності одним з джерелпорядку пріоритету):
90
-
91
- 1. **CHANGELOG / Releases репозиторію модуля** — найшвидше і найточніше. Адресу репозиторію взяти з `package.json` модуля у `node_modules/<name>/package.json` (поле `repository`). Дістати релізи між старою і новою версією.
92
- 2. **Git-різниця в `node_modules/<name>`** — якщо CHANGELOG відсутній або неінформативний, порівняти попередню версію (з кешу bun: `~/.bun/install/cache/<name>@<old-version>/`) з новою (`node_modules/<name>/`) через `diff -r` по `dist/` / `*.d.ts` / публічних entry-points з `exports`.
93
- 3. **Якщо немає кешованої старої версії** — встановити її окремо в тимчасову теку (`mkdir -p /tmp/taze-old && cd /tmp/taze-old && bun add <name>@<old-version>`) і порівняти.
94
-
95
- Цікавлять: видалені/перейменовані експорти, змінені сигнатури функцій, змінені типи, змінена поведінка за замовчуванням, видалені CLI-прапорці.
68
+ Для кожного запису зі списку зібрати фактичні відмінності: CHANGELOG/Releases репозиторію пакета (джерело адреси — у фрагменті гілки), а без них — diff публічного API між кешованою старою версією і новою. Цікавлять: видалені/перейменовані експорти, змінені сигнатури, змінені типи, змінена поведінка за замовчуванням, видалені CLI-прапорці.
96
69
 
97
70
  ### 5. Перевірити сумісність з кодом проекту
98
71
 
99
- Для кожного breaking change знайти його використання в коді проекту:
100
-
101
- ```bash
102
- rg -n "<імпорт|функція|опція>" --type ts --type js --type vue
103
- ```
104
-
105
- Класифікувати:
106
-
107
- - **сумісно** — проект не використовує зачеплене API → нічого не робити.
108
- - **несумісно** — використання знайдено → перейти до п. 6.
72
+ Для кожного breaking change знайти використання зачепленого API в коді (`rg -n` по мові гілки). Класифікувати: **сумісно** — не використовується → нічого не робити; **несумісно** — перейти до п. 6.
109
73
 
110
74
  ### 6. Рефакторинг несумісних місць
111
75
 
112
- Для кожного несумісного місця — застосувати міграцію згідно з changelog модуля (перейменувати імпорт, оновити сигнатуру виклику, замінити видалену опцію еквівалентом тощо). Після правок:
113
-
114
- ```bash
115
- npx @7n/rules lint
116
- bun run typecheck # якщо є
117
- bun test # якщо є
118
- ```
76
+ Застосувати міграцію згідно з changelog пакета (перейменувати імпорт, оновити сигнатуру виклику, замінити видалену опцію еквівалентом тощо). Після правок — прогнати перевірки гілки (лінт/typecheck/тести — конкретні команди у фрагменті). Нетривіальна або неоднозначна міграція — **не вгадувати**: TODO у коді з посиланням на CHANGELOG і запис у звіт як ручна дія.
119
77
 
120
78
  ### 7. Прибрати тимчасові файли
121
79
 
122
- ```bash
123
- rm package.json.taze-bak bun.lock.taze-bak
124
- ```
125
-
126
- (І решту бекапів воркспейсів, якщо створювались.)
80
+ Видалити всі `.taze-bak`-бекапи, створені на кроці 1.
127
81
 
128
82
  ### 8. Звіт користувачу
129
83
 
130
- Коротко в одному повідомленні:
84
+ Для кожної обробленої гілки — окрема секція:
131
85
 
132
- - **Оновлено (minor/patch):** кількість пакетів (`minorPatch` із кроку 3), без деталей.
86
+ - **Оновлено (minor/patch):** кількість пакетів, без деталей.
133
87
  - **Major-оновлення:** список `<name>: <old> → <new>` з посиланням на release notes.
134
88
  - **Зрефакторено автоматично:** список файлів і коротко що саме змінено.
135
89
  - **Потребує ручного втручання:** список TODO з причиною (нетривіальна міграція / неоднозначність / падіння тестів).
136
- - **Стан перевірок:** `lint` / `typecheck` / `test` — pass/fail з номером рядка, де впало.
137
-
138
- Для кожної обробленої екосистемної гілки — окрема секція з тим самим переліком (оновлено / major / зрефакторено / потребує ручного втручання) і власними перевірками (див. фрагменти плагінів наприкінці файлу).
90
+ - **Стан перевірок:** перевірки гілки — pass/fail з номером рядка, де впало.
139
91
 
140
92
  ## Примітка
141
93
 
142
94
  - Не запускати `npx @7n/rules lint` паралельно з іншими ESLint-задачами — діє правило з кореневого `CLAUDE.md`.
143
95
  - Якщо проект — `npm/` пакет цього репо, після змін у `package.json` / коді треба підняти `version` і додати запис у `CHANGELOG.md` згідно з `npm/CLAUDE.md`.
144
- - При великій кількості major-оновлень розбити PR по одному модулю на коміт — щоб `git bisect` залишався корисним. Це стосується і пакетів екосистемних гілок окремо від npm-пакетів.
96
+ - При великій кількості major-оновлень розбити PR по одному модулю на коміт — щоб `git bisect` залишався корисним. Це стосується пакетів кожної гілки окремо.
@@ -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: 6a269363
6
+ crc: df10e67a
7
7
  model: openai-codex/gpt-5.4-mini
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -12,30 +12,27 @@ 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`), завантажені з плагінів (`@7n/rules-lang-rust`, `@7n/rules-lang-python`, …; extension-point `taze`) вбудованих провайдерів немає. Файл виконує реальні файлові операції (бекапи) і запускає зовнішні команди (`bunx`, `bun`, `git`, команди провайдерів) — не read-only.
15
+ Оркеструє taze як чистий цикл по EcosystemProvider-ах (фаза 5a spec lang-plugins-extraction: ядро двигун без мовної специфіки). ВСІ екосистеми, включно з npm/bun (`@7n/rules-lang-js`), приходять з плагінів (extension-point `taze`): для кожного провайдераdetect available backup bump diff, далі по одному ізольованому виклику раннера на кожен major-запис (`promptFor` провайдера), потім cleanup і композиція Markdown-звіту. Виконує файлові операції і запускає зовнішні команди (git, npx для worktree-бутстрапу, команди провайдерів) — не read-only.
16
16
 
17
17
  ## Поведінка
18
18
 
19
- - **buildDependencyPrompt** — формує промпт ОДНОГО ітеративного виклику (лише перевірка сумісності й рефакторинг) для одного major-пакета npm/bun.
20
19
  - **callRunner** — диспетчер одного ітеративного виклику: `pi` — вбудований pi-агент (текст через `deps.out`), `cursor`/`codex` — napi-міст ACP (`@7n/llm-lib/acp`).
21
- - **backupWorkspacePackageFiles** / **cleanupBackups** — бекап і прибирання `package.json` кожного воркспейсу.
22
20
  - **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) і повертає звіт.
21
+ - **formatReport** — Markdown-звіт: рівноправна секція на кожну екосистему з manifests (без manifests — тиша) + загальний підсумок; без окремого LLM-виклику.
22
+ - **runTazeOrchestrator** — повний прогін: гарантує ізольований worktree (авто-створює `<branch>-taze` через `npx @7n/mt worktree create`, після завершення переносить зміни назад і прибирає авто-створене дерево), проганяє кожного провайдера наскрізь і повертає `{ ok, report, ecosystems }`. Без жодного taze-провайдера попередження (для npm/bun-гілки потрібен `@7n/rules-lang-js`).
23
+ - **bringChangesBackToOriginal** / **removeAutoCreatedWorktree** — перенесення незакомічених змін з авто-створеного worktree у вихідне дерево і його прибирання.
25
24
 
26
25
  ## Публічний API
27
26
 
28
- - buildDependencyPrompt — промпт одного npm-major-запису.
29
27
  - callRunner — виклик обраного раннера (`pi`/`cursor`/`codex`) з одним промптом.
30
- - backupWorkspacePackageFiles / cleanupBackups — бекап/прибирання `package.json` воркспейсів.
31
28
  - loadPluginTazeProviders — валідні EcosystemProvider-и з handler-модулів плагінів.
32
- - formatReport — фінальний Markdown-звіт із npm-результатів і записів екосистем.
29
+ - formatReport — фінальний Markdown-звіт із записів екосистем.
33
30
  - runTazeOrchestrator — повна оркестрація taze; `deps.ecosystemProviders` повністю замінює список провайдерів (для тестів).
31
+ - bringChangesBackToOriginal / removeAutoCreatedWorktree — життєвий цикл авто-створеного worktree.
34
32
 
35
33
  ## Гарантії поведінки
36
34
 
37
- - Виконує файлові операції (копіювання/видалення бекапів) і запускає зовнішні команди — НЕ read-only.
38
- - Перед будь-якою мутацією перевіряє, що `cwd` ізольований worktree (`assertRunningInWorktree`), інакше кидає виняток.
39
- - npm/bun-гілка активна лише за кореневим package.json: на чисто-Python/Rust репо вона тихо пропускається (лог + без npm-рядків у звіті), а не валить прогін через `bun install → exit 1`.
35
+ - Виконує файлові операції і запускає зовнішні команди — НЕ read-only.
36
+ - Кроки оркестрації виконуються лише в ізольованому worktree; авто-створене дерево прибирається у `finally` навіть при винятку, зміни переносяться назад.
40
37
  - Виняток усередині одного провайдера (bump/diff/команда) не зупиняє інших — фіксується в `error` запису екосистеми й у звіті; `ok` результату тоді false.
41
38
  - Падіння одного пакета в ізольованому виклику раннера не втрачає прогрес по інших записах.
@@ -1,8 +1,5 @@
1
1
  /** @see ./docs/orchestrate.md */
2
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
3
  import { pathToFileURL } from 'node:url'
7
4
 
8
5
  import {
@@ -13,38 +10,9 @@ import {
13
10
  import { assertEcosystemProvider } from '../../../scripts/lib/plugin-api.mjs'
14
11
  import { readNRulesConfigLite } from '../../../scripts/lib/read-n-rules-config-lite.mjs'
15
12
  import { getHandlers, resolvePlugins } from '../../../scripts/lib/resolve-plugins.mjs'
16
- import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
17
- import { collectTazeDiff } from './diff.mjs'
18
13
 
19
14
  export { bringChangesBackToOriginal, removeAutoCreatedWorktree } from '../../../scripts/lib/auto-worktree.mjs'
20
15
 
21
- /** Суфікс бекапу package.json — той самий, що й у `diff.mjs`/кроці 1 SKILL.md. */
22
- const BACKUP_SUFFIX = '.taze-bak'
23
-
24
- /**
25
- * Промпт ОДНОГО ітеративного виклику — лише кроки 4-6 SKILL.md (breaking
26
- * changes → сумісність коду → рефакторинг) для ОДНОГО major-пакета. Кроки
27
- * 1-3/7/8 виконує оркестратор детерміновано, без LLM.
28
- * @param {{workspace: string, pkg: string, from: string, to: string}} entry запис major-diff (з `collectTazeDiff`)
29
- * @returns {string} готовий промпт
30
- */
31
- export function buildDependencyPrompt({ workspace, pkg, from, to }) {
32
- return [
33
- '# Major-оновлення одного пакета: перевірка сумісності й рефакторинг',
34
- '',
35
- `Пакет \`${pkg}\` у воркспейсі \`${workspace}\`: **${from} → ${to}** — вже застосовано в package.json/bun.lock (кроки 1-3 виконано детерміновано, без тебе). Твоя задача — лише breaking-changes-перевірка й, за потреби, рефакторинг.`,
36
- '',
37
- '## Кроки',
38
- `1. Зібрати breaking changes цього оновлення: CHANGELOG/Releases репозиторію модуля (поле \`repository\` у \`node_modules/${pkg}/package.json\`), або git/diff між закешованою старою версією (\`~/.bun/install/cache/${pkg}@<стара-версія>/\`) і новою (\`node_modules/${pkg}/\`).`,
39
- `2. Знайти використання зачепленого API в коді проєкту (\`rg -n\` по імпортах/викликах \`${pkg}\`).`,
40
- '3. Сумісно — нічого не робити. Несумісно — застосувати міграцію (перейменувати імпорт, оновити сигнатуру виклику, замінити видалену опцію еквівалентом).',
41
- '4. Якщо були правки — запусти `npx @7n/rules lint`, typecheck/test якщо є в проєкті.',
42
- '5. Нетривіальна/неоднозначна міграція — не вгадуй, залиш TODO-коментар із посиланням на CHANGELOG.',
43
- '',
44
- 'У відповіді одним абзацом підсумуй: сумісно / зрефакторено (які файли) / TODO (чому).'
45
- ].join('\n')
46
- }
47
-
48
16
  /**
49
17
  * Диспетчер одного ітеративного виклику на обраний раннер. `pi` — вбудований
50
18
  * pi-агент (`@7n/llm-lib/agent-skill`; текст перехоплюється через `deps.out`,
@@ -87,58 +55,6 @@ export async function callRunner(runner, prompt, cwd, deps = {}) {
87
55
  }
88
56
  }
89
57
 
90
- /**
91
- * Синхронно виконує детерміновану команду (bunx/bun), кидає з
92
- * exit-кодом+stderr при провалі.
93
- * @param {string} cmd бінарник
94
- * @param {string[]} args аргументи
95
- * @param {string} cwd робочий каталог
96
- * @param {typeof spawnSync} spawnFn інжект для тестів
97
- * @returns {string} stdout
98
- */
99
- function runCommand(cmd, args, cwd, spawnFn) {
100
- const result = spawnFn(cmd, args, { cwd, encoding: 'utf8' })
101
- if (result.status !== 0) {
102
- throw new Error(`${cmd} ${args.join(' ')} → exit ${result.status}: ${result.stderr || result.stdout}`)
103
- }
104
- return result.stdout
105
- }
106
-
107
- /**
108
- * Бекапить package.json кожного воркспейсу (крок 1 SKILL.md) — потрібно для
109
- * класифікації major/minor через `collectTazeDiff` після bump-у.
110
- * @param {string} cwd корінь репо
111
- * @param {{ getMonorepoPackageRootDirs?: (cwd: string) => Promise<string[]>, copyFile?: (src: string, dest: string) => Promise<void> }} [deps] інжекти
112
- * @returns {Promise<string[]>} відносні шляхи воркспейсів, що мали package.json
113
- */
114
- export async function backupWorkspacePackageFiles(cwd, deps = {}) {
115
- const getRoots = deps.getMonorepoPackageRootDirs ?? getMonorepoPackageRootDirs
116
- const copy = deps.copyFile ?? copyFile
117
- const roots = await getRoots(cwd)
118
- const backedUp = []
119
- for (const ws of roots) {
120
- const pkgPath = join(cwd, ws, 'package.json')
121
- if (!existsSync(pkgPath)) continue
122
- await copy(pkgPath, `${pkgPath}${BACKUP_SUFFIX}`)
123
- backedUp.push(ws)
124
- }
125
- return backedUp
126
- }
127
-
128
- /**
129
- * Прибирає бекапи package.json після завершення (крок 7 SKILL.md).
130
- * @param {string} cwd корінь репо
131
- * @param {string[]} workspaces воркспейси з бекапом (з `backupWorkspacePackageFiles`)
132
- * @param {{ rm?: (path: string, opts?: object) => Promise<void> }} [deps] інжект
133
- * @returns {Promise<void>}
134
- */
135
- export async function cleanupBackups(cwd, workspaces, deps = {}) {
136
- const remove = deps.rm ?? rm
137
- for (const ws of workspaces) {
138
- await remove(join(cwd, ws, `package.json${BACKUP_SUFFIX}`), { force: true })
139
- }
140
- }
141
-
142
58
  /**
143
59
  * Завантажує EcosystemProvider-и з активних плагінів проєкту: `.n-rules.json`
144
60
  * (або автодетект за файловими сигналами — `pyproject.toml` → lang-python) →
@@ -265,50 +181,39 @@ function appendEcosystemSection(lines, eco) {
265
181
 
266
182
  /**
267
183
  * Компонує підсумковий звіт (крок 8 SKILL.md) детерміновано з результатів
268
- * ітерацій — без окремого LLM-виклику для самого звіту.
269
- * @param {{
270
- * minorPatch: number,
271
- * totalChanged: number,
272
- * results: Array<{pkg:string, workspace:string, from:string, to:string, ok:boolean, error:string|null}>,
273
- * ecosystems?: Array<object>,
274
- * npmPresent?: boolean
275
- * }} args дані звіту (`ecosystems` — записи з `runEcosystem`, по одному на провайдера;
276
- * `npmPresent: false` — репо без кореневого package.json, npm-рядки не друкуються)
184
+ * ітерацій — без окремого LLM-виклику для самого звіту. Усі екосистеми
185
+ * (включно з npm/bun — плагін `@7n/rules-lang-js`, фаза 5a) — рівноправні
186
+ * секції; екосистема без manifests — тиша.
187
+ * @param {{ ecosystems?: Array<object> }} args записи з `runEcosystem`, по одному на провайдера
277
188
  * @returns {string} markdown-звіт
278
189
  */
279
- export function formatReport({ minorPatch, totalChanged, results, ecosystems = [], npmPresent = true }) {
280
- const lines = ['## taze: підсумок', '']
281
- if (npmPresent) {
282
- lines.push(`- **Оновлено (minor/patch):** ${minorPatch}`, `- **Major-оновлення:** ${results.length}`)
283
- for (const r of results) {
284
- lines.push(formatResultLine(r, r.workspace))
285
- }
286
- }
190
+ export function formatReport({ ecosystems = [] }) {
191
+ const lines = ['## taze: підсумок']
287
192
 
288
- let ecosystemsTotal = 0
193
+ let total = 0
289
194
  for (const eco of ecosystems) {
290
- ecosystemsTotal += appendEcosystemSection(lines, eco)
195
+ total += appendEcosystemSection(lines, eco)
291
196
  }
292
197
 
293
- lines.push('', `- **Всього змінено:** ${totalChanged + ecosystemsTotal}`)
198
+ lines.push('', `- **Всього змінено:** ${total}`)
294
199
  return lines.join('\n')
295
200
  }
296
201
 
297
202
  /**
298
- * Оркеструє taze: детерміновані кроки (бекап масовий bump → diff →
299
- * прибирання звіт) без LLM, і по одному ізольованому, обмеженому по
300
- * обсягу виклику `callRunner` на кожен major-пакет (кроки 4-6 SKILL.md)
301
- * замість одного величезного непрозорого ходу на весь монорепо. npm/bun-гілка
302
- * вбудована; решта екосистем EcosystemProvider-и, завантажені з плагінів
303
- * (`@7n/rules-lang-*`, extension-point `taze`; фаза 2 spec — Rust теж плагін).
304
- * Падіння одного пакета/однієї екосистеми не втрачає прогрес по інших.
203
+ * Оркеструє taze: чистий цикл по EcosystemProvider-ах (кроки 1-3/7/8
204
+ * детерміновано в провайдері, кроки 4-6 по одному ізольованому виклику
205
+ * `callRunner` на кожен major-запис) замість одного величезного непрозорого
206
+ * ходу на весь монорепо. Ядро — двигун без мовної специфіки (фаза 5a spec):
207
+ * ВСІ екосистеми, включно з npm/bun (`@7n/rules-lang-js`), приходять з
208
+ * плагінів (extension-point `taze`). Падіння одного пакета/однієї екосистеми
209
+ * не втрачає прогрес по інших.
305
210
  * @param {{
306
211
  * cwd?: string,
307
212
  * runner?: 'pi' | 'cursor' | 'codex',
308
213
  * log?: (line: string) => void,
309
- * deps?: { spawnFn?: typeof spawnSync, collectTazeDiff?: (cwd: string) => Promise<object>, callRunner?: (runner: string, prompt: string, cwd: string, deps: object) => Promise<{ok: boolean, text: string, error: string|null}>, ecosystemProviders?: object[] } & Record<string, unknown>
214
+ * deps?: { spawnFn?: typeof spawnSync, callRunner?: (runner: string, prompt: string, cwd: string, deps: object) => Promise<{ok: boolean, text: string, error: string|null}>, ecosystemProviders?: object[] } & Record<string, unknown>
310
215
  * }} [options] опції + інжекти для тестів (`deps.ecosystemProviders` повністю замінює список провайдерів)
311
- * @returns {Promise<{ ok: boolean, report: string, results: Array<object>, ecosystems: Array<object> }>} результат
216
+ * @returns {Promise<{ ok: boolean, report: string, ecosystems: Array<object> }>} результат
312
217
  */
313
218
  export async function runTazeOrchestrator(options = {}) {
314
219
  const runner = options.runner ?? 'pi'
@@ -325,55 +230,22 @@ export async function runTazeOrchestrator(options = {}) {
325
230
  const cwd = worktree.cwd
326
231
 
327
232
  try {
328
- // npm/bun-гілка активна лише за кореневим package.json — на чисто-Python/Rust
329
- // репо `bun install` падає з exit 1, і без цього гейта весь прогін гинув би
330
- // до екосистемних провайдерів. Той самий принцип «тиші», що й для мовних
331
- // екосистем: немає сигналу — немає ані кроків, ані згадки у звіті.
332
- const npmPresent = existsSync(join(cwd, 'package.json'))
333
- let diff = { major: [], minorPatch: 0, totalChanged: 0 }
334
- const results = []
335
- if (npmPresent) {
336
- log('📦 Бекап package.json...')
337
- const backedUpWorkspaces = await backupWorkspacePackageFiles(cwd, deps)
338
-
339
- log('⬆️ bunx taze -w -r latest...')
340
- runCommand('bunx', ['taze', '-w', '-r', 'latest'], cwd, spawnFn)
341
- log('📥 bun install...')
342
- runCommand('bun', ['install'], cwd, spawnFn)
343
-
344
- const collectDiff = deps.collectTazeDiff ?? collectTazeDiff
345
- diff = await collectDiff(cwd)
346
- log(`🔍 diff: ${diff.major.length} major, ${diff.minorPatch} minor/patch`)
347
-
348
- for (const entry of diff.major) {
349
- log(`🔧 ${entry.pkg} (${entry.workspace}): ${entry.from} → ${entry.to}...`)
350
- const outcome = await call(runner, buildDependencyPrompt(entry), cwd, deps)
351
- results.push({ ...entry, ...outcome })
352
- log(outcome.ok ? ` ✅ ${entry.pkg}` : ` ❌ ${entry.pkg}: ${outcome.error}`)
353
- }
354
-
355
- await cleanupBackups(cwd, backedUpWorkspaces, deps)
356
- } else {
357
- log('⏭ npm/bun: кореневого package.json немає — гілка пропущена')
358
- }
359
-
360
233
  const providers = deps.ecosystemProviders ?? (await loadPluginTazeProviders(cwd, log, deps))
234
+ if (providers.length === 0) {
235
+ log(
236
+ '⏭ Жодного taze-провайдера: жоден активний плагін не надає extension-point `taze` (для npm/bun-гілки потрібен @7n/rules-lang-js)'
237
+ )
238
+ }
361
239
  const ecosystems = []
362
240
  for (const provider of providers) {
363
241
  ecosystems.push(await runEcosystem(provider, { cwd, runner, log, deps, spawnFn, call }))
364
242
  }
365
243
 
366
- const report = formatReport({
367
- minorPatch: diff.minorPatch,
368
- totalChanged: diff.totalChanged,
369
- results,
370
- ecosystems,
371
- npmPresent
372
- })
244
+ const report = formatReport({ ecosystems })
373
245
  log(report)
374
246
 
375
247
  const ecosystemsOk = ecosystems.every(eco => eco.error === null && eco.results.every(r => r.ok))
376
- return { ok: results.every(r => r.ok) && ecosystemsOk, report, results, ecosystems }
248
+ return { ok: ecosystemsOk, report, ecosystems }
377
249
  } finally {
378
250
  // Лише для АВТОстворених worktree — якщо викликач уже сидів у своєму
379
251
  // worktree (worktree.autoCreated === false), це не наш worktree і не
@@ -1,140 +0,0 @@
1
- /** @see ./docs/diff.md */
2
- import { existsSync } from 'node:fs'
3
- import { readFile } from 'node:fs/promises'
4
- import { join } from 'node:path'
5
-
6
- import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
7
-
8
- /** Поля package.json із залежностями, які порівнюємо. */
9
- const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
10
-
11
- /** Дефолтний суфікс бекапу, який створює крок 1 скіла (`cp package.json package.json.taze-bak`). */
12
- const DEFAULT_BACKUP_SUFFIX = '.taze-bak'
13
-
14
- // Заякорено на початок (після можливих range-операторів `^~>=<`, пробілів, `v`),
15
- // щоб НЕ ловити версію всередині protocol-specifier-ів (`workspace:1.0.0`, `npm:x@1.2.3`).
16
- const SEMVER_RE = /^[\s~^>=<v]*(\d+)\.(\d+)\.(\d+)/
17
-
18
- /**
19
- * Парсить semver-ядро зі specifier-а (ігнорує range-префікси `^`/`~`/`>=` тощо).
20
- * @param {string} spec версійний specifier із package.json
21
- * @returns {{major:number, minor:number, patch:number}|null} ядро або null для не-semver (`workspace:*`, git-url, `*`)
22
- */
23
- export function parseVersion(spec) {
24
- if (typeof spec !== 'string') return null
25
- const m = SEMVER_RE.exec(spec)
26
- if (!m) return null
27
- return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) }
28
- }
29
-
30
- /**
31
- * Чи є перехід `from → to` breaking за caret-семантикою (змінилась найлівіша
32
- * ненульова компонента).
33
- * @param {{major:number,minor:number,patch:number}} from стара версія
34
- * @param {{major:number,minor:number,patch:number}} to нова версія
35
- * @returns {boolean} true — major/breaking
36
- */
37
- export function isBreaking(from, to) {
38
- if (from.major !== to.major) return true
39
- if (from.major > 0) return false
40
- if (from.minor !== to.minor) return true
41
- if (from.minor > 0) return false
42
- return from.patch !== to.patch
43
- }
44
-
45
- /**
46
- * Порівнює два package.json-обʼєкти й повертає зміни залежностей.
47
- * @param {object} oldPkg розпарсений старий package.json (бекап)
48
- * @param {object} newPkg розпарсений новий package.json
49
- * @param {string} workspace мітка воркспейсу (`.` для кореня)
50
- * @returns {{major: Array<{workspace:string, pkg:string, from:string, to:string}>, minorPatch:number}} зміни
51
- */
52
- export function diffPackageJson(oldPkg, newPkg, workspace) {
53
- const major = []
54
- let minorPatch = 0
55
- for (const field of DEP_FIELDS) {
56
- const oldDeps = oldPkg?.[field]
57
- const newDeps = newPkg?.[field]
58
- if (!oldDeps || !newDeps) continue
59
- for (const [pkg, from] of Object.entries(oldDeps)) {
60
- const to = newDeps[pkg]
61
- if (to === undefined || to === from) continue
62
- const fromV = parseVersion(from)
63
- const toV = parseVersion(to)
64
- if (fromV && toV && isBreaking(fromV, toV)) {
65
- major.push({ workspace, pkg, from, to })
66
- } else {
67
- minorPatch += 1
68
- }
69
- }
70
- }
71
- return { major, minorPatch }
72
- }
73
-
74
- /**
75
- * Читає JSON-файл або повертає null, якщо файл відсутній / невалідний.
76
- * @param {string} path абсолютний шлях
77
- * @returns {Promise<object|null>} розпарсений обʼєкт або null
78
- */
79
- async function readJsonOrNull(path) {
80
- if (!existsSync(path)) return null
81
- try {
82
- return JSON.parse(await readFile(path, 'utf8'))
83
- } catch {
84
- return null
85
- }
86
- }
87
-
88
- /**
89
- * Збирає diff по всьому монорепо: для кожного воркспейсу порівнює
90
- * `<ws>/package.json` з `<ws>/package.json<backupSuffix>`.
91
- * @param {string} cwd корінь репозиторію
92
- * @param {string} [backupSuffix] суфікс бекап-файлу
93
- * @returns {Promise<{major: Array<{workspace:string, pkg:string, from:string, to:string}>, minorPatch:number, totalChanged:number, comparedWorkspaces:number}>} агрегований diff
94
- */
95
- export async function collectTazeDiff(cwd, backupSuffix = DEFAULT_BACKUP_SUFFIX) {
96
- const roots = await getMonorepoPackageRootDirs(cwd)
97
- const major = []
98
- let minorPatch = 0
99
- let comparedWorkspaces = 0
100
- for (const ws of roots) {
101
- const dir = join(cwd, ws)
102
- const oldPkg = await readJsonOrNull(join(dir, `package.json${backupSuffix}`))
103
- const newPkg = await readJsonOrNull(join(dir, 'package.json'))
104
- if (!oldPkg || !newPkg) continue
105
- comparedWorkspaces += 1
106
- const res = diffPackageJson(oldPkg, newPkg, ws)
107
- major.push(...res.major)
108
- minorPatch += res.minorPatch
109
- }
110
- return { major, minorPatch, totalChanged: major.length + minorPatch, comparedWorkspaces }
111
- }
112
-
113
- const USAGE = 'Usage: n-rules taze diff [--backup-suffix <suffix>]'
114
-
115
- /**
116
- * CLI: `n-rules taze diff` друкує компактний JSON зі списком major-оновлень і
117
- * лічбою minor/patch. Read-only.
118
- * @param {string[]} args аргументи після `taze`
119
- * @param {string} [cwd] корінь репозиторію (ін'єкція для тестів)
120
- * @returns {Promise<number>} exit code
121
- */
122
- export async function runTazeCli(args, cwd = process.cwd()) {
123
- if (args[0] !== 'diff') {
124
- console.error(USAGE)
125
- return 1
126
- }
127
- const flagAt = args.indexOf('--backup-suffix')
128
- const backupSuffix = flagAt === -1 ? DEFAULT_BACKUP_SUFFIX : args[flagAt + 1]
129
- if (!backupSuffix) {
130
- console.error(USAGE)
131
- return 1
132
- }
133
- const diff = await collectTazeDiff(cwd, backupSuffix)
134
- if (diff.comparedWorkspaces === 0) {
135
- console.error(`✗ Не знайдено жодного package.json${backupSuffix} — спершу зроби бекап (крок 1 скіла).`)
136
- return 1
137
- }
138
- process.stdout.write(`${JSON.stringify(diff)}\n`)
139
- return 0
140
- }
@@ -1,55 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: diff.mjs
4
- resource: npm/skills/taze/js/diff.mjs
5
- docgen:
6
- crc: a70297a7
7
- score: 100
8
- ---
9
-
10
- parseVersion
11
- Парсить версію з specifier.
12
-
13
- isBreaking
14
- Перевіряє перехід версій згідно з caret-семантикою.
15
-
16
- diffPackageJson
17
- Порівнює об'єкти package.json і повертає зміни залежностей.
18
-
19
- collectTazeDiff
20
- Збирає diff по всьому монорепо порівнюючи бекап та новий файл.
21
-
22
- runTazeCli
23
- Друкує результат diff у форматі JSON.
24
-
25
- ## Поведінка
26
-
27
- parseVersion
28
- Парсить версію з specifier
29
-
30
- isBreaking
31
- Перевіряє перехід версій згідно з caret-семантикою
32
-
33
- diffPackageJson
34
- Порівнює об'єкти package.json і повертає зміни залежностей
35
-
36
- collectTazeDiff
37
- Збирає diff по всьому монорепо порівнюючи бекап та новий файл
38
-
39
- runTazeCli
40
- Друкує результат diff у форматі JSON
41
-
42
- ## Публічний API
43
-
44
- parseVersion — витягує версію з specifier-а, ігноруючи range-префікси.
45
- isBreaking — визначає, чи є перехід між версіями breaking за caret-семантикою (зміна найлівішої ненульової частини).
46
- diffPackageJson — порівнює два package.json-об'єкти та генерує список змін залежностей.
47
- collectTazeDiff — збирає різницю між версіями всього монорепо, порівнюючи `package.json` з його резервною копією у кожному воркспейсі.
48
- runTazeCli — виконує команду `n-rules taze diff` для виведення компактного JSON зі списком major-оновлень та лічилки minor/patch.
49
-
50
- ## Гарантії поведінки
51
-
52
- - Read-only: файл не виконує операцій запису у файлову систему.
53
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
54
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
55
- - Не звертається до мережі.