@7n/rules 1.18.0 → 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 CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## [1.18.0] - 2026-07-18
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.18.0",
3
+ "version": "1.18.1",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -47,21 +47,32 @@ function dropWorktreeCheckouts(paths) {
47
47
 
48
48
  /**
49
49
  * Визначає git base для scoped-перевірок без зовнішнього runtime-стану.
50
- * Пріоритет: локальна `main`, потім `origin/main`; якщо обидві відсутні,
51
- * повертає null і caller порівнює лише робоче дерево з HEAD.
52
- * Явний `baseRef` (CI: `--base origin/main` після fetch) вимикає каскад
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 бази замість каскаду main→origin/main
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
- for (const ref of baseRef ? [baseRef] : ['main', 'origin/main']) {
65
+ const mergeBaseWith = ref => {
60
66
  const result = spawnSync('git', ['merge-base', 'HEAD', ref], { cwd, encoding: 'utf8' })
61
- const base = result.status === 0 && !result.error ? result.stdout.trim() : ''
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: 254d852a
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
- - Пріоритет: локальна `main`, потім `origin/main`; явний другий аргумент `baseRef` (CI: `--base origin/main`) вимикає каскад merge-base рахується лише проти нього.
23
- - Якщо ref-и відсутні, повертає null, і викликаючий порівнює лише робоче дерево з HEAD.
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`.