@nitra/cursor 12.3.1 → 12.3.2
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
|
+
## [12.3.2] - 2026-06-20
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- fix-каскад: per-tier timeout (локалі fail-fast ~45s замість стіни 120s, env N_LOCAL_FIX_TIMEOUT_MS/N_CLOUD_FIX_TIMEOUT_MS) + хмарний транспортний збій (pi ETIMEDOUT/spawn) обриває драбину замість ескалації на cloud-avg — не палиться avg-бюджет
|
|
8
|
+
|
|
3
9
|
## [12.3.1] - 2026-06-20
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: llm-worker.mjs
|
|
4
4
|
resource: npm/scripts/lib/fix/llm-worker.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 857c510e
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
@@ -18,7 +18,7 @@ docgen:
|
|
|
18
18
|
|
|
19
19
|
## Публічний API
|
|
20
20
|
|
|
21
|
-
- `runLlmWorker(ruleId, violationOutput, projectRoot, opts)` — виправляє одне порушення; `opts`: `model`, `feedback`, `caller
|
|
21
|
+
- `runLlmWorker(ruleId, violationOutput, projectRoot, opts)` — виправляє одне порушення; `opts`: `model`, `feedback`, `caller`, `timeoutMs` (per-tier ліміт виклику; драбина задає коротший для локальних рунгів). Повертає `{ ok, error, changes, diagnosis }`.
|
|
22
22
|
|
|
23
23
|
## Гарантії поведінки
|
|
24
24
|
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: orchestrator.mjs
|
|
4
4
|
resource: npm/scripts/lib/fix/orchestrator.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: d327ab6d
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
@@ -19,7 +19,8 @@ docgen:
|
|
|
19
19
|
- рунг `local-min` — перший прохід без feedback;
|
|
20
20
|
- рунг `local-min-retry` — той самий локальний тир, але з feedback попереднього рунга (попередні зміни + залишковий violation);
|
|
21
21
|
- рунги `cloud-min` / `cloud-avg` — хмарні моделі (через pi), теж із feedback.
|
|
22
|
-
|
|
22
|
+
Кожен рунг має per-tier `timeoutMs`: локальні **fail-fast** (`N_LOCAL_FIX_TIMEOUT_MS`, дефолт 45s — не палити стіну 120s на повільному локальному inference), хмарні — повний (`N_CLOUD_FIX_TIMEOUT_MS`, дефолт 120s).
|
|
23
|
+
5. Достроковий вихід драбини: systemic-помилка локального тиру пропускає рунги тієї ж моделі; відсутній API-ключ на хмарному обриває драбину; хмарний транспортний збій (pi таймаут/spawn) обриває драбину, щоб не палити avg-бюджет на ту саму стіну; вичерпаний avg-кеп пропускає avg-рунг (із записом у лог).
|
|
23
24
|
6. Після обробки всіх правил — фінальна перевірка. Усі чисті → успіх; інакше — ознака нерозв'язаних.
|
|
24
25
|
|
|
25
26
|
## Публічний API
|
|
@@ -117,11 +117,12 @@ function buildPrompt(ruleId, ruleMdc, output, files, feedback = null) {
|
|
|
117
117
|
* @param {string} prompt текст промпта
|
|
118
118
|
* @param {string} model назва моделі (provider/id, `omlx/...` або '')
|
|
119
119
|
* @param {string} caller мітка викликача для wire-trace (`fix:<rule>:<rung>`)
|
|
120
|
+
* @param {number} [timeoutMs] ліміт виклику (драбина задає per-tier; undefined → дефолт callLlm)
|
|
120
121
|
* @returns {{ text: string, error?: string }} текст відповіді або повідомлення про помилку
|
|
121
122
|
*/
|
|
122
|
-
function callModel(prompt, model, caller) {
|
|
123
|
+
function callModel(prompt, model, caller, timeoutMs) {
|
|
123
124
|
try {
|
|
124
|
-
return { text: callLlm([{ role: 'user', content: prompt }], model, { timeoutMs
|
|
125
|
+
return { text: callLlm([{ role: 'user', content: prompt }], model, { timeoutMs, caller }) }
|
|
125
126
|
} catch (error) {
|
|
126
127
|
const msg = String(error.message)
|
|
127
128
|
if (API_KEY_RE.test(msg)) {
|
|
@@ -146,9 +147,10 @@ function callModel(prompt, model, caller) {
|
|
|
146
147
|
* @param {string} ruleId ID правила
|
|
147
148
|
* @param {string} violationOutput output з fix check для цього rule
|
|
148
149
|
* @param {string} projectRoot абсолютний шлях до кореня проєкту
|
|
149
|
-
* @param {{ model?: string, feedback?: object|null, caller?: string }} opts опції:
|
|
150
|
+
* @param {{ model?: string, feedback?: object|null, caller?: string, timeoutMs?: number }} opts опції:
|
|
150
151
|
* `model` — перевизначення моделі; `feedback` — контекст попереднього рунга
|
|
151
|
-
* драбини (retry-with-feedback); `caller` — мітка для wire-trace
|
|
152
|
+
* драбини (retry-with-feedback); `caller` — мітка для wire-trace; `timeoutMs` —
|
|
153
|
+
* per-tier ліміт виклику (драбина: локалі fail-fast, хмара повний)
|
|
152
154
|
* @returns {{ ok: boolean, error?: string, changes: Array<{path:string}>, diagnosis: string|null }}
|
|
153
155
|
* статус виправлення, помилка, запропоновані зміни і само-аналіз моделі
|
|
154
156
|
*/
|
|
@@ -156,6 +158,7 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
|
156
158
|
const model = opts.model ?? MODEL
|
|
157
159
|
const feedback = opts.feedback ?? null
|
|
158
160
|
const caller = opts.caller ?? 'fix'
|
|
161
|
+
const timeoutMs = opts.timeoutMs
|
|
159
162
|
|
|
160
163
|
// 1. Читаємо rule .mdc
|
|
161
164
|
const mdcPath = join(projectRoot, '.cursor', 'rules', `n-${ruleId}.mdc`)
|
|
@@ -166,7 +169,7 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
|
166
169
|
|
|
167
170
|
// 3. Будуємо prompt і викликаємо модель
|
|
168
171
|
const prompt = buildPrompt(ruleId, ruleMdc, violationOutput, files, feedback)
|
|
169
|
-
const { text, error: modelError } = callModel(prompt, model, caller)
|
|
172
|
+
const { text, error: modelError } = callModel(prompt, model, caller, timeoutMs)
|
|
170
173
|
|
|
171
174
|
if (modelError) return { ok: false, error: modelError, changes: [], diagnosis: null }
|
|
172
175
|
if (!text) return { ok: false, error: 'model returned empty response', changes: [], diagnosis: null }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @see ./docs/orchestrator.md */
|
|
2
2
|
|
|
3
|
+
import { env } from 'node:process'
|
|
3
4
|
import { runFixCheck } from './run-fix-check.mjs'
|
|
4
5
|
import { runT0AutoCli } from './t0.mjs'
|
|
5
6
|
import { logEscalation } from './escalation-log.mjs'
|
|
@@ -13,9 +14,24 @@ import { CLOUD_AVG, CLOUD_MIN, LOCAL_MIN } from '../../../lib/models.mjs'
|
|
|
13
14
|
*/
|
|
14
15
|
const DEFAULT_MAX_AVG = 3
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Timeout одного LLM-виклику за тиром. Локальні рунги **fail-fast**: не палити
|
|
19
|
+
* стіну 120s на повільному 4b (curl exit 28) — швидше абортнути й ескалувати.
|
|
20
|
+
* Хмарні — повний. Перевизначення: `N_LOCAL_FIX_TIMEOUT_MS` / `N_CLOUD_FIX_TIMEOUT_MS`.
|
|
21
|
+
*/
|
|
22
|
+
const LOCAL_TIMEOUT_MS = Number(env.N_LOCAL_FIX_TIMEOUT_MS) || 45_000
|
|
23
|
+
const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) || 120_000
|
|
24
|
+
|
|
16
25
|
/** Маркер дружнього повідомлення про відсутній API-ключ (з `llm-worker.callModel`). */
|
|
17
26
|
const NO_KEY_RE = /немає ключа|api key/i
|
|
18
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Хмарний транспорт (pi) упав на рівні процесу: таймаут/spawn-помилка. Стіна часу
|
|
30
|
+
* однакова для всіх cloud-рунгів (та сама pi-транспортна стіна), а cloud-avg — інша
|
|
31
|
+
* модель, не більший timeout. Ескалація на неї лише спалить avg-бюджет → обрив.
|
|
32
|
+
*/
|
|
33
|
+
const CLOUD_TRANSPORT_RE = /etimedout|timed out|pi error/i
|
|
34
|
+
|
|
19
35
|
/**
|
|
20
36
|
* Будує драбину ескалації за наявними тирами (спека 2026-06-19-fix-escalation-cascade):
|
|
21
37
|
* 1. `local-min` — `N_LOCAL_MIN_MODEL`, перший прохід;
|
|
@@ -24,14 +40,14 @@ const NO_KEY_RE = /немає ключа|api key/i
|
|
|
24
40
|
* 4. `cloud-avg` — `N_CLOUD_AVG_MODEL` (через pi), з feedback, під avg-кепом.
|
|
25
41
|
* Рунги з незаданим тиром (`''`) відсіюються — драбина стискається до доступних.
|
|
26
42
|
* @param {{ localMin: string, cloudMin: string, cloudAvg: string }} models тири з env
|
|
27
|
-
* @returns {Array<{ tier: string, model: string, feedback: boolean, local: boolean, isAvg: boolean }>} драбина
|
|
43
|
+
* @returns {Array<{ tier: string, model: string, feedback: boolean, local: boolean, isAvg: boolean, timeoutMs: number }>} драбина
|
|
28
44
|
*/
|
|
29
45
|
export function buildLadder({ localMin, cloudMin, cloudAvg }) {
|
|
30
46
|
return [
|
|
31
|
-
{ tier: 'local-min', model: localMin, feedback: false, local: true, isAvg: false },
|
|
32
|
-
{ tier: 'local-min-retry', model: localMin, feedback: true, local: true, isAvg: false },
|
|
33
|
-
{ tier: 'cloud-min', model: cloudMin, feedback: true, local: false, isAvg: false },
|
|
34
|
-
{ tier: 'cloud-avg', model: cloudAvg, feedback: true, local: false, isAvg: true }
|
|
47
|
+
{ tier: 'local-min', model: localMin, feedback: false, local: true, isAvg: false, timeoutMs: LOCAL_TIMEOUT_MS },
|
|
48
|
+
{ tier: 'local-min-retry', model: localMin, feedback: true, local: true, isAvg: false, timeoutMs: LOCAL_TIMEOUT_MS },
|
|
49
|
+
{ tier: 'cloud-min', model: cloudMin, feedback: true, local: false, isAvg: false, timeoutMs: CLOUD_TIMEOUT_MS },
|
|
50
|
+
{ tier: 'cloud-avg', model: cloudAvg, feedback: true, local: false, isAvg: true, timeoutMs: CLOUD_TIMEOUT_MS }
|
|
35
51
|
].filter(r => r.model)
|
|
36
52
|
}
|
|
37
53
|
|
|
@@ -40,6 +56,8 @@ export function buildLadder({ localMin, cloudMin, cloudAvg }) {
|
|
|
40
56
|
* - `break` — відсутній API-ключ на хмарному (інші хмарні рунги теж без ключа);
|
|
41
57
|
* - `skip-model` — systemic-помилка локального тиру (memory-guard/auth/down): повтор
|
|
42
58
|
* тієї ж моделі марний → пропустити рунги з цим model.
|
|
59
|
+
* - `break` — також хмарний транспорт упав (pi таймаут/spawn): решта cloud-рунгів
|
|
60
|
+
* під тією ж стіною → обрив, щоб не палити avg-бюджет.
|
|
43
61
|
* @param {{ local: boolean }} rung поточний рунг
|
|
44
62
|
* @param {string|null|undefined} error помилка виклику worker
|
|
45
63
|
* @returns {'break'|'skip-model'|null} дія для драбини
|
|
@@ -48,6 +66,7 @@ function decideAfterFailure(rung, error) {
|
|
|
48
66
|
if (!error) return null
|
|
49
67
|
if (NO_KEY_RE.test(error)) return 'break'
|
|
50
68
|
if (rung.local && classifyOmlxError(error) === 'systemic') return 'skip-model'
|
|
69
|
+
if (!rung.local && CLOUD_TRANSPORT_RE.test(error)) return 'break'
|
|
51
70
|
return null
|
|
52
71
|
}
|
|
53
72
|
|
|
@@ -93,7 +112,8 @@ export async function escalateRule(rule, cwd, deps) {
|
|
|
93
112
|
const res = worker.runLlmWorker(rule.ruleId, currentViolation, cwd, {
|
|
94
113
|
model: rung.model,
|
|
95
114
|
feedback: rung.feedback ? feedback : null,
|
|
96
|
-
caller: `fix:${rule.ruleId}:${rung.tier}
|
|
115
|
+
caller: `fix:${rule.ruleId}:${rung.tier}`,
|
|
116
|
+
timeoutMs: rung.timeoutMs
|
|
97
117
|
})
|
|
98
118
|
if (rung.isAvg) avgUsed++
|
|
99
119
|
|