@7n/rules 1.49.22 → 1.49.23

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.49.23] - 2026-07-27
4
+
5
+ ### Fixed
6
+
7
+ - Treat reconcile diffs containing only release entries as patch-equivalent
8
+
3
9
  ## [1.49.22] - 2026-07-27
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.49.22",
3
+ "version": "1.49.23",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: orchestrate.mjs
4
4
  resource: npm/skills/git-reconcile/js/orchestrate.mjs
5
5
  docgen:
6
- crc: 59ea7f47
6
+ crc: 69749e90
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
@@ -24,6 +24,8 @@ docgen:
24
24
 
25
25
  `commitPendingChanges` створює додатковий commit лише для staged remediation. Чистий index після перенесення branch-source є валідним, коли корисні commits уже присутні в `HEAD`. Порожній PR check rollup вважається непідтвердженим, а не успішним.
26
26
 
27
+ `hasOnlyChangeEntries` відсікає tree diff, у якому лишилися тільки `*/.changes/*.md`: такий результат вважається `patch-equivalent`, не створює PR і дозволяє cleanup source branch.
28
+
27
29
  `pruneForensicDependencies` прибирає лише rebuildable `node_modules` зі збереженого forensic worktree. `cleanupSource` видаляє лише точний доказово безпечний branch/stash. Cleanup review-source дозволений після `drop` або коли всі його групи завершились `pr-created` чи `patch-equivalent`; `failed` і всі `pr-checks-*` outcomes лишають source та forensic worktree.
28
30
 
29
31
  ## Публічний API
@@ -36,6 +38,7 @@ docgen:
36
38
  - `captureBehaviorBaseline`, `captureCachedBehaviorBaseline` — фіксують і кешують test baseline.
37
39
  - `validateBehaviorState`, `validateFinalProjectGates`, `remediateBehaviorState` — виконують behavioral та canonical gates.
38
40
  - `commitPendingChanges` — комітить лише staged remediation, не вимагаючи порожнього commit для вже перенесених commits.
41
+ - `hasOnlyChangeEntries` — розпізнає release-metadata-only diff, який не потребує PR.
39
42
  - `pruneForensicDependencies` — звільняє rebuildable dependencies без втрати Git evidence.
40
43
  - `classifyPullRequestChecks`, `verifyPullRequestReadiness` — класифікують CI відносно base commit.
41
44
  - `formatOutcomeCounts`, `formatReport` — формують точний deterministic summary.
@@ -49,6 +52,7 @@ docgen:
49
52
  - PR вважається створеним успішно лише після terminal green checks.
50
53
  - Порожній PR check rollup fail-closed зберігає forensic worktree.
51
54
  - Forensic worktree зберігає Git evidence без накопичення `node_modules`.
55
+ - Diff лише з `.changes` не створює PR і не блокує cleanup source branch.
52
56
  - CI regression, baseline-red або непідтверджений стан завершують orchestration non-zero і зберігають forensic refs/worktree.
53
57
  - Cleanup не видаляє protected, open-PR, kept, failed або `pr-checks-*` sources.
54
58
  - `spawnSync.error`, зокрема `ENOENT`, зберігається в command diagnostics.
@@ -25,6 +25,7 @@ const REF_HEADS_RE = /^refs\/heads\//
25
25
  const REF_ORIGIN_RE = /^refs\/remotes\/origin\//
26
26
  const RENAME_DELETE_CONFLICT_RE = /^CONFLICT \(rename\/delete\): .+? renamed to (.+?) in .+?, but deleted/
27
27
  const SOURCE_CODE_RE = /\.(?:js|mjs|ts|vue|rs|py)$/
28
+ const CHANGE_ENTRY_RE = /(^|\/)\.changes\/[^/]+\.md$/
28
29
  const WHITESPACE_RE = /\s+/
29
30
  const ACP_PROGRESS_ENV = 'N_LLM_ACP_PROGRESS'
30
31
  const REF_INVENTORY_FORMAT = ['%(refname)', '%00', '%(object', 'name)', '%00', '%(committer', 'date:iso-strict)'].join(
@@ -1008,6 +1009,16 @@ export function sourceDirectories(paths) {
1008
1009
  return [...new Set(paths.filter(path => SOURCE_CODE_RE.test(path)).map(path => dirname(path)))].toSorted()
1009
1010
  }
1010
1011
 
1012
+ /**
1013
+ * Визначає технічний залишок, який містить лише release entries. Такі файли
1014
+ * не доводять окремої корисної поведінки й не мають породжувати PR.
1015
+ * @param {string[]} paths змінені шляхи відносно policy base
1016
+ * @returns {boolean} чи всі зміни лежать безпосередньо у `.changes/`
1017
+ */
1018
+ export function hasOnlyChangeEntries(paths) {
1019
+ return paths.length > 0 && paths.every(path => CHANGE_ENTRY_RE.test(path))
1020
+ }
1021
+
1011
1022
  /**
1012
1023
  * Збирає tracked і untracked code directories відносно policy base ref.
1013
1024
  * @param {string} cwd worktree
@@ -1022,6 +1033,40 @@ function changedPaths(cwd, spawnFn) {
1022
1033
  return [...new Set([...tracked, ...untracked])]
1023
1034
  }
1024
1035
 
1036
+ /**
1037
+ * @param {string} cwd worktree
1038
+ * @param {typeof spawnSync} spawnFn інжект
1039
+ * @returns {boolean} чи tree diff складається лише з release entries
1040
+ */
1041
+ function hasOnlyChangeEntriesFromBase(cwd, spawnFn) {
1042
+ return hasOnlyChangeEntries(changedPaths(cwd, spawnFn))
1043
+ }
1044
+
1045
+ /**
1046
+ * Прибирає no-op або change-only worktree до дорогих behavioral/CI gates.
1047
+ * @param {object} args контекст materialized worktree
1048
+ * @returns {{status:string,branch:string,rationale?:string}|null} terminal outcome
1049
+ */
1050
+ function discardPatchEquivalentWorktree(args) {
1051
+ const { worktree, rootCwd, spawnFn, onProgress, validated = false } = args
1052
+ if (!hasChangesFromBase(worktree.cwd, spawnFn)) {
1053
+ onProgress('remove no-op worktree')
1054
+ removeReconcileWorktree(worktree, rootCwd, spawnFn)
1055
+ return { status: 'patch-equivalent', branch: worktree.branch }
1056
+ }
1057
+ if (!hasOnlyChangeEntriesFromBase(worktree.cwd, spawnFn)) return null
1058
+
1059
+ onProgress('remove change-only worktree')
1060
+ removeReconcileWorktree(worktree, rootCwd, spawnFn)
1061
+ return {
1062
+ status: 'patch-equivalent',
1063
+ branch: worktree.branch,
1064
+ rationale: validated
1065
+ ? 'Після Git validation лишилися тільки release entries у .changes/'
1066
+ : 'Після перенесення лишилися тільки release entries у .changes/'
1067
+ }
1068
+ }
1069
+
1025
1070
  /**
1026
1071
  * Повертає директорії зміненого коду.
1027
1072
  * @param {string} cwd worktree
@@ -1635,11 +1680,8 @@ async function createPullRequest(args) {
1635
1680
  log,
1636
1681
  onProgress
1637
1682
  })
1638
- if (!hasChangesFromBase(worktree.cwd, spawnFn)) {
1639
- onProgress('remove no-op worktree')
1640
- removeReconcileWorktree(worktree, rootCwd, spawnFn)
1641
- return { status: 'patch-equivalent', branch: worktree.branch }
1642
- }
1683
+ const appliedOutcome = discardPatchEquivalentWorktree({ worktree, rootCwd, spawnFn, onProgress })
1684
+ if (appliedOutcome) return appliedOutcome
1643
1685
  const verification =
1644
1686
  changedSourceDirectories(worktree.cwd, spawnFn).length > 0
1645
1687
  ? await finalizeBehavior({
@@ -1659,11 +1701,14 @@ async function createPullRequest(args) {
1659
1701
  if (unresolved.length > 0) throw new Error(`Нерозв'язані конфлікти: ${unresolved.join(', ')}`)
1660
1702
  git(['diff', '--check'], worktree.cwd, spawnFn)
1661
1703
  git(['add', '-A'], worktree.cwd, spawnFn)
1662
- if (!hasChangesFromBase(worktree.cwd, spawnFn)) {
1663
- onProgress('remove no-op worktree')
1664
- removeReconcileWorktree(worktree, rootCwd, spawnFn)
1665
- return { status: 'patch-equivalent', branch: worktree.branch }
1666
- }
1704
+ const validatedOutcome = discardPatchEquivalentWorktree({
1705
+ worktree,
1706
+ rootCwd,
1707
+ spawnFn,
1708
+ onProgress,
1709
+ validated: true
1710
+ })
1711
+ if (validatedOutcome) return validatedOutcome
1667
1712
  onProgress('delta lint')
1668
1713
  const finalGates = await passFinalProjectGates({
1669
1714
  cwd: worktree.cwd,