@7n/rules 1.49.9 → 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 +13 -0
- package/package.json +1 -1
- package/rules/test/coverage/concern.json +1 -0
- package/rules/test/coverage/fix-worker.mjs +18 -9
- package/rules/text/run-v8r/docs/main.md +4 -2
- package/rules/text/run-v8r/main.mjs +25 -3
- package/schemas/concern.json +5 -0
- package/schemas/v8r-catalog.json +2 -0
- package/scripts/lib/concern-meta.mjs +4 -1
- package/scripts/lib/lint-surface/run-fix.mjs +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
9
|
+
## [1.49.10] - 2026-07-26
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Зареєстровано схему для .kubescape-exceptions.json у каталозі v8r
|
|
14
|
+
- Поважати .n-rules.json:ignore у full-скані v8r
|
|
15
|
+
|
|
3
16
|
## [1.49.9] - 2026-07-26
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/package.json
CHANGED
|
@@ -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
|
-
*
|
|
22
|
-
*
|
|
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
|
-
/**
|
|
31
|
-
const
|
|
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
|
|
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() +
|
|
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
|
}
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/text/run-v8r/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 327472a0
|
|
7
7
|
model: manual
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -23,10 +23,11 @@ docgen:
|
|
|
23
23
|
- `writeResolvedV8rConfig()` — записує `{ cacheTtl, customCatalog: { schemas } }` у `RESOLVED_V8R_CONFIG_PATH` (побічний ефект: файлова операція запису, поза rollback-механізмом лінт-пайплайна).
|
|
24
24
|
- `runV8rWithGlobs(globs?)` — послідовно запускає `v8r` для кожного glob-у з `V8R_CONFIG_FILE`, вказаним на резольвнутий конфіг; повертає `{ code, detail }` (не голий код): при `code` 0/98 вивід приховано, `detail` порожній; інакше `detail` друкується й повертається код першого невдалого прогону.
|
|
25
25
|
- `runV8rWithFiles(files)` — delta-режим: один запуск `v8r` по конкретних існуючих шляхах (код 98 неможливий), порожній список — одразу `{ code: 0, detail: '' }`.
|
|
26
|
+
- `findV8rFiles(root)` — збирає full-scope JSON/YAML/TOML-файли через спільний обхід: поважає `.gitignore` та `.n-rules.json:ignore`, повертає відносні шляхи для `v8r`.
|
|
26
27
|
- `extractFailureLines(combinedText)` — фільтрує `ℹ`-шум і bunx install-вивід з об'єднаного `stdout+stderr` одного запуску `v8r`, лишаючи предметну деталь (`✖ …`-заголовки, ajv-причини). Обидва потоки об'єднуються навмисно: `v8r` непослідовно розкидає деталь між ними залежно від типу помилки (schema violation — причина в stdout, заголовок у stderr; "не знайдено схему" — усе в stderr).
|
|
27
28
|
- `warnAboutRemoteSchemaFallback(stderrText)` — парсить stderr `v8r` і на кожен файл, чию схему знайдено мережевим fallback-ом (schemastore, не customCatalog), друкує в stdout пораду додати схему в каталог.
|
|
28
29
|
- `stripBunNodeShimDirs(pathValue)` — прибирає з PATH shim-теки `bun-node-*`, які `bun run --bun` додає з підміненим `node`: дочірній `v8r` (node-shebang) інакше виконувався б під bun і падав на непідтримуваному `node:sea`. Дочірній процес `v8r` завжди отримує очищений PATH.
|
|
29
|
-
- `lint(ctx)` — detector `text/run-v8r`: `ctx.files` → delta по відфільтрованих розширеннях, без `ctx.files` → full
|
|
30
|
+
- `lint(ctx)` — detector `text/run-v8r`: `ctx.files` → delta по відфільтрованих розширеннях, без `ctx.files` → full через `findV8rFiles`, без `.n-rules.json:ignore`. Порушення несе `detail` (якщо є) у тексті `fail()`-повідомлення — без нього LLM fix-worker бачив лише "щось не пройшло" й незмінно провалював усі rung-и драбини (не мав інформації, що саме виправляти).
|
|
30
31
|
|
|
31
32
|
## Публічний API
|
|
32
33
|
|
|
@@ -34,6 +35,7 @@ docgen:
|
|
|
34
35
|
- `resolveCustomCatalogSchemas()` — обчислені схеми customCatalog.
|
|
35
36
|
- `writeResolvedV8rConfig()` — матеріалізація тимчасового конфігу, повертає його шлях.
|
|
36
37
|
- `runV8rWithGlobs(globs?)`, `runV8rWithFiles(files)` — точки входу перевірки (full/delta), повертають `{ code, detail }`.
|
|
38
|
+
- `findV8rFiles(root)` — full-scope збір файлів із повагою до `.gitignore` та `.n-rules.json:ignore`.
|
|
37
39
|
- `extractFailureLines(combinedText)` — фільтр noise-рядків для деталі провалу.
|
|
38
40
|
- `warnAboutRemoteSchemaFallback(stderrText)` — попередження про мережевий fallback схем.
|
|
39
41
|
- `stripBunNodeShimDirs(pathValue)` — PATH без shim-тек `bun-node-*` (для дочірнього v8r).
|
|
@@ -46,13 +46,15 @@
|
|
|
46
46
|
*/
|
|
47
47
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
48
48
|
import { tmpdir } from 'node:os'
|
|
49
|
-
import { basename, delimiter, dirname, isAbsolute, join } from 'node:path'
|
|
49
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative } from 'node:path'
|
|
50
50
|
import { env } from 'node:process'
|
|
51
51
|
import { fileURLToPath } from 'node:url'
|
|
52
52
|
|
|
53
53
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
54
|
+
import { loadCursorIgnorePaths } from '../../../scripts/lib/load-cursor-config.mjs'
|
|
54
55
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
55
56
|
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
57
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
56
58
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
57
59
|
|
|
58
60
|
/** Розширення, які валідує v8r — фільтр delta-списку файлів у `lint(ctx)`. */
|
|
@@ -346,6 +348,25 @@ export async function runV8rWithFiles(files, verbose = false) {
|
|
|
346
348
|
return { code: r.code === 98 ? 0 : r.code, detail: r.detail }
|
|
347
349
|
}
|
|
348
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Збирає файли форматів v8r для full-режиму, поважаючи `.gitignore` та
|
|
353
|
+
* `.n-rules.json:ignore`, як інші full-scope concern-и.
|
|
354
|
+
* @param {string} root абсолютний корінь репозиторію
|
|
355
|
+
* @returns {Promise<string[]>} відносні до root шляхи файлів для v8r
|
|
356
|
+
*/
|
|
357
|
+
export async function findV8rFiles(root) {
|
|
358
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
359
|
+
const files = []
|
|
360
|
+
await walkDir(
|
|
361
|
+
root,
|
|
362
|
+
file => {
|
|
363
|
+
if (V8R_EXT_RE.test(file)) files.push(relative(root, file))
|
|
364
|
+
},
|
|
365
|
+
ignorePaths
|
|
366
|
+
)
|
|
367
|
+
return files.toSorted((a, b) => a.localeCompare(b))
|
|
368
|
+
}
|
|
369
|
+
|
|
349
370
|
/**
|
|
350
371
|
* Будує violation-повідомлення з опційною деталлю `✖ …`-рядків v8r — без неї LLM fix-worker
|
|
351
372
|
* бачить лише "щось не пройшло" й не має інформації, який файл/поле саме порушує схему.
|
|
@@ -358,7 +379,7 @@ function v8rFailMessage(detail) {
|
|
|
358
379
|
}
|
|
359
380
|
|
|
360
381
|
/**
|
|
361
|
-
* Detector text/run-v8r: read-only v8r по `ctx.files` (delta) або
|
|
382
|
+
* Detector text/run-v8r: read-only v8r по `ctx.files` (delta) або всіх форматних файлах поза `.n-rules.json:ignore` (full).
|
|
362
383
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
|
|
363
384
|
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат detector-а
|
|
364
385
|
*/
|
|
@@ -369,7 +390,8 @@ export async function lint(ctx) {
|
|
|
369
390
|
const verbose = ctx.verbose === true
|
|
370
391
|
|
|
371
392
|
if (ctx.files === undefined) {
|
|
372
|
-
const
|
|
393
|
+
const files = await findV8rFiles(ctx.cwd)
|
|
394
|
+
const { code, detail } = await runV8rWithFiles(files, verbose)
|
|
373
395
|
if (code !== 0) fail(v8rFailMessage(detail), 'v8r')
|
|
374
396
|
return reporter.result()
|
|
375
397
|
}
|
package/schemas/concern.json
CHANGED
|
@@ -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,
|
package/schemas/v8r-catalog.json
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|