@7n/rules 1.49.10 → 1.49.11

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.11] - 2026-07-26
4
+
5
+ ### Fixed
6
+
7
+ - Додано окремий 180-секундний cloud budget для survived-mutant coverage batch і deferred решти batch-ів
8
+
3
9
  ## [1.49.10] - 2026-07-26
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.49.10",
3
+ "version": "1.49.11",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -2,6 +2,7 @@
2
2
  "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
3
  "fixability": "code",
4
4
  "skipLocalTier": true,
5
+ "cloudTimeoutMs": 180000,
5
6
  "lint": {
6
7
  "scope": "per-file",
7
8
  "glob": ["**/*.{js,mjs,cjs,jsx,ts,tsx,vue}"]
@@ -18,8 +18,9 @@
18
18
  * (assert порту вимагає лише detect/collect/collectPerFile). Хуки отримують
19
19
  * FixContext-поля (model/tier/timeoutMs/recordWrite/chain/signal/feedback);
20
20
  * recordWrite прокидається до кожного місця запису (rollback-контракт ladder-а).
21
- * Дедлайн: DEADLINE_FRACTION від ctx.timeoutMs гейтить СТАРТ наступного хука
22
- * (як js/eslint fix-worker); залишок бюджету передається хуку як timeoutMs.
21
+ * Coverage-specific cloud rung дає survived-mutant agent batch повний budget:
22
+ * outer runner backstop лишається ×1.25. Один survived batch стартує за rung;
23
+ * решта є deferred telemetry, а не timeout/no-op failure.
23
24
  * Власних retry-циклів немає — success визначає canonical re-detect runner-а.
24
25
  * @typedef {import('../../../scripts/lib/lint-surface/types.mjs').FixWorkerFn} FixWorkerFn
25
26
  * @typedef {import('../../../scripts/lib/lint-surface/types.mjs').FixContext} FixContext
@@ -27,8 +28,8 @@
27
28
  */
28
29
  import { resolveProviders } from './main.mjs'
29
30
 
30
- /** Частка ctx.timeoutMs, після якої не стартує наступний хук (запас до backstop ×1.25). */
31
- const DEADLINE_FRACTION = 0.8
31
+ /** Один survived-mutant agent batch за coverage rung. */
32
+ const SURVIVED_BATCHES_PER_RUNG = 1
32
33
 
33
34
  /** `.vue`-файли → generateStories, решта → generateTests. */
34
35
  const VUE_FILE_RE = /\.vue$/
@@ -59,7 +60,7 @@ export function groupViolations(violations) {
59
60
  */
60
61
  function hookCtx(ctx, deadlineAt) {
61
62
  const remaining = deadlineAt ? Math.max(1000, deadlineAt - Date.now()) : ctx.timeoutMs
62
- const workerDeadlineMs = ctx.timeoutMs ? Math.round(ctx.timeoutMs * DEADLINE_FRACTION) : null
63
+ const workerDeadlineMs = ctx.timeoutMs ?? null
63
64
  return {
64
65
  ...ctx,
65
66
  timeoutMs: remaining,
@@ -68,7 +69,8 @@ function hookCtx(ctx, deadlineAt) {
68
69
  coverageTimeout: {
69
70
  requestedMs: ctx.timeoutMs ?? null,
70
71
  workerDeadlineMs,
71
- effectiveHookTimeoutMs: remaining ?? null
72
+ effectiveHookTimeoutMs: remaining ?? null,
73
+ survivedBatchesPerRung: SURVIVED_BATCHES_PER_RUNG
72
74
  }
73
75
  }
74
76
  }
@@ -76,7 +78,7 @@ function hookCtx(ctx, deadlineAt) {
76
78
  /** @type {FixWorkerFn} */
77
79
  export async function fixWorker(violations, ctx, deps = {}) {
78
80
  // Дедлайн фіксується ДО резолву провайдерів — їх завантаження теж у бюджеті рунга.
79
- const deadlineAt = ctx.timeoutMs ? Date.now() + Math.round(ctx.timeoutMs * DEADLINE_FRACTION) : null
81
+ const deadlineAt = ctx.timeoutMs ? Date.now() + ctx.timeoutMs : null
80
82
  const expired = () => deadlineAt !== null && Date.now() >= deadlineAt
81
83
 
82
84
  const providers = await (deps.resolveProviders ?? resolveProviders)(ctx.cwd)
@@ -88,6 +90,7 @@ export async function fixWorker(violations, ctx, deps = {}) {
88
90
  const touchedFiles = []
89
91
  /** @type {Array<{provider: string, hook: string, files: string[], error: string}>} */
90
92
  const failed = []
93
+ const deferred = []
91
94
  /**
92
95
  * Викликає опційний fix-hook провайдера, збирає touchedFiles; виняток хука не
93
96
  * валить решту хуків/провайдерів — success визначає canonical re-detect.
@@ -101,6 +104,7 @@ export async function fixWorker(violations, ctx, deps = {}) {
101
104
  try {
102
105
  const res = await provider[hook]({ ...args, cwd: ctx.cwd, ctx: hookCtx(ctx, deadlineAt) })
103
106
  touchedFiles.push(...(res?.touchedFiles ?? []))
107
+ deferred.push(...(res?.deferred ?? []))
104
108
  for (const failure of res?.failed ?? []) {
105
109
  const error = failure?.error ?? 'невідома причина'
106
110
  const files = failure?.files ?? []
@@ -115,9 +119,14 @@ export async function fixWorker(violations, ctx, deps = {}) {
115
119
  }
116
120
 
117
121
  for (const provider of providers) {
122
+ // Survived mutants мають власний cloud budget: не витрачаємо його на інші
123
+ // LLM hooks перед єдиним агентним batch-ем цього rung-а.
124
+ if (survived.length > 0) {
125
+ await runHook(provider, 'fixSurvived', { survived })
126
+ continue
127
+ }
118
128
  if (jsFiles.length > 0) await runHook(provider, 'generateTests', { files: jsFiles })
119
129
  if (vueFiles.length > 0) await runHook(provider, 'generateStories', { files: vueFiles })
120
- if (survived.length > 0) await runHook(provider, 'fixSurvived', { survived })
121
130
  // Після генерації: тести, що впали (зокрема щойно згенеровані), чиняться
122
131
  // окремим хуком — свіжий vitest-прогін усередині провайдера. Без жодної
123
132
  // роботи вище (порожній профіль violations) хук не стартує.
@@ -126,5 +135,5 @@ export async function fixWorker(violations, ctx, deps = {}) {
126
135
  }
127
136
  }
128
137
 
129
- return { touchedFiles, failed }
138
+ return { touchedFiles, failed, deferred }
130
139
  }
@@ -23,6 +23,11 @@
23
23
  "type": "boolean",
24
24
  "description": "Дефолт `false` (поле можна опускати). `true` пропускає local-min/local-min-retry rung-и LLM-ladder-а для цього concern-а — перша спроба одразу йде на cloud-min. Для concern-ів, де local-tier (слабка локальна модель, короткий бюджет) емпірично майже завжди лише витрачає час rung-а без результату, перш ніж ladder однаково ескалює далі."
25
25
  },
26
+ "cloudTimeoutMs": {
27
+ "type": "integer",
28
+ "minimum": 1,
29
+ "description": "Опційний budget одного cloud rung-а лише для цього concern-а. Замінює timeout cloud-min/cloud-avg у ladder, не змінюючи глобальний `N_CLOUD_FIX_TIMEOUT_MS` або інші concern-и."
30
+ },
26
31
  "check": {
27
32
  "type": "boolean",
28
33
  "const": true,
@@ -38,6 +38,8 @@ import { join } from 'node:path'
38
38
  * ladder-а — ladder одразу стартує з cloud-min. Для concern-ів, де local-tier емпірично не встигає
39
39
  * дати результат у межах свого бюджету (напр. js/eslint — 0/12 успіхів у реальному прогоні, лише
40
40
  * витрачений час).
41
+ * @property {number|undefined} cloudTimeoutMs опційний budget cloud rung-а concern-а;
42
+ * не змінює глобальні timeout-и ladder-а чи інші concern-и.
41
43
  */
42
44
 
43
45
  /**
@@ -131,7 +133,8 @@ export async function readConcernMeta(concernDir, name) {
131
133
  lint,
132
134
  requiresCapability,
133
135
  fixability: parseFixability(raw.fixability),
134
- skipLocalTier: raw.skipLocalTier === true
136
+ skipLocalTier: raw.skipLocalTier === true,
137
+ cloudTimeoutMs: Number.isInteger(raw.cloudTimeoutMs) && raw.cloudTimeoutMs > 0 ? raw.cloudTimeoutMs : undefined
135
138
  }
136
139
  }
137
140
 
@@ -610,7 +610,9 @@ function noteT0Phase(t0, chainExtra, touchedAbs) {
610
610
  }
611
611
 
612
612
  /**
613
- * Ladder concern-а: повний, або без local-tier rung-ів (`concern.skipLocalTier`)
613
+ * Ladder concern-а: повний, або без local-tier rung-ів (`concern.skipLocalTier`).
614
+ * Concern може замінити cloud budget своїм `cloudTimeoutMs`; глобальний ladder та
615
+ * всі інші concern-и лишаються без змін.
614
616
  * concern-и, де local-min/local-min-retry емпірично не встигають дати результат
615
617
  * у межах свого бюджету (concern-meta.mjs).
616
618
  * @param {Rung[]} ladder Повний ladder pipeline-у.
@@ -618,7 +620,10 @@ function noteT0Phase(t0, chainExtra, touchedAbs) {
618
620
  * @returns {Rung[]} Ladder, застосовний до цього concern-а.
619
621
  */
620
622
  function selectLadder(ladder, concern) {
621
- return concern.skipLocalTier ? ladder.filter(rung => !rung.local) : ladder
623
+ const selected = concern.skipLocalTier ? ladder.filter(rung => !rung.local) : ladder
624
+ return concern.cloudTimeoutMs
625
+ ? selected.map(rung => (rung.local ? rung : { ...rung, timeoutMs: concern.cloudTimeoutMs }))
626
+ : selected
622
627
  }
623
628
 
624
629
  /**