@nitra/cursor 12.3.0 → 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 +12 -0
- package/package.json +2 -2
- package/rules/doc-files/js/docgen-crc.mjs +4 -31
- package/rules/doc-files/js/docgen-extract-anchors.mjs +1 -15
- package/rules/doc-files/js/docgen-files-batch.mjs +1 -12
- package/rules/doc-files/js/docgen-judge-measure.mjs +10 -18
- package/rules/doc-files/js/docs/docgen-crc.md +1 -1
- package/rules/doc-files/js/docs/docgen-extract-anchors.md +1 -1
- package/rules/doc-files/js/docs/docgen-files-batch.md +1 -1
- package/rules/doc-files/js/docs/docgen-judge-measure.md +1 -1
- package/rules/doc-files/js/docs/lint.md +1 -1
- package/rules/doc-files/js/lint.mjs +1 -16
- package/rules/lint/js/docs/orchestrate.md +1 -1
- package/rules/lint/js/orchestrate.mjs +1 -15
- package/rules/text/lint/cspell-fix.mjs +2 -1
- package/rules/text/lint/docs/cspell-fix.md +15 -9
- package/scripts/lib/adr/docs/normalize-cli.md +15 -15
- package/scripts/lib/adr/normalize-cli.mjs +3 -2
- package/scripts/lib/fix/docs/llm-worker.md +2 -2
- package/scripts/lib/fix/docs/orchestrator.md +3 -2
- package/scripts/lib/fix/llm-worker.mjs +8 -5
- package/scripts/lib/fix/orchestrator.mjs +26 -6
- package/skills/doc-aggregate/js/docgen-ignore.mjs +1 -8
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## [12.3.1] - 2026-06-20
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- npm-module конформність: module-level JSDoc → pointer (`/** @see ./docs/… */`) у docgen-crc/extract-anchors/files-batch/judge-measure, doc-files/lint, lint/orchestrate, doc-aggregate/docgen-ignore; doc-CRC перештамповано; eslint --fix-стан прийнято як канон
|
|
14
|
+
|
|
3
15
|
## [12.3.0] - 2026-06-19
|
|
4
16
|
|
|
5
17
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "12.3.
|
|
3
|
+
"version": "12.3.2",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -68,4 +68,4 @@
|
|
|
68
68
|
"skills": "skills",
|
|
69
69
|
"extensions": ".pi-template/extensions"
|
|
70
70
|
}
|
|
71
|
-
}
|
|
71
|
+
}
|
|
@@ -1,34 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CRC32 джерела + YAML-frontmatter файлової документації.
|
|
3
|
-
*
|
|
4
|
-
* Кожна файлова дока несе у frontmatter контрольну суму байтів джерела на момент
|
|
5
|
-
* генерації. Це детермінований маркер застарілості: `crc32(поточне джерело)` звіряється
|
|
6
|
-
* з `crc` у доці — розбіжність (або відсутня дока) означає, що дока відстала від коду.
|
|
7
|
-
* CRC не залежить від git-стану (rebase, незакомічене, гілки), тож придатний і для
|
|
8
|
-
* per-edit hook (бачить лише змінений файл), і для повного сканування.
|
|
9
|
-
*
|
|
10
|
-
* Degraded-маркер (ADR 260610-2228): якщо локальний конвеєр не дотягнув до порогу
|
|
11
|
-
* якості, дока все одно пишеться, а frontmatter додатково несе `score` (det-оцінка)
|
|
12
|
-
* та `issues` (коди проблем). CRC при цьому свіжий — Stop-гейт не блокує задачі через
|
|
13
|
-
* слабкість моделі; борг видимий через `check --degraded` і автоматично доретраюється
|
|
14
|
-
* наступним `gen` (рівно один раз на версію джерела — далі `retried: true` у frontmatter).
|
|
15
|
-
*
|
|
16
|
-
* Frontmatter — єдиний дозволений виняток із правила «чистий Markdown без HTML»:
|
|
17
|
-
* це машинні метадані, не контент. Формат:
|
|
18
|
-
*
|
|
19
|
-
* ---
|
|
20
|
-
* docgen:
|
|
21
|
-
* source: src/lib/foo.js
|
|
22
|
-
* crc: a3f1c9e0
|
|
23
|
-
* model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
24
|
-
* score: 55
|
|
25
|
-
* issues: short-behavior,internal-name:bar
|
|
26
|
-
* ---
|
|
27
|
-
*
|
|
28
|
-
* `model` — повний id моделі-генератора (як повертає resolveModel, із префіксом
|
|
29
|
-
* провайдера). Пасивна метадата: маркер «віку» доки за моделлю на додачу до CRC
|
|
30
|
-
* джерела. На staleness НЕ впливає — звіряється лише `crc`.
|
|
31
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-crc.md */
|
|
32
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
33
3
|
import { basename, extname } from 'node:path'
|
|
34
4
|
import { crc32 as zlibCrc32 } from 'node:zlib'
|
|
@@ -173,6 +143,9 @@ export function buildDocFrontmatter(source, crc, quality = null, model = null) {
|
|
|
173
143
|
*/
|
|
174
144
|
const LEADING_H1_RE = /^#[^\n]*\n+/u
|
|
175
145
|
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
*/
|
|
176
149
|
export function stampDoc(md, source, crc, quality = null, model = null) {
|
|
177
150
|
const { body } = parseDocFrontmatter(md)
|
|
178
151
|
const cleanBody = body.replace(LEADING_NEWLINES_RE, '').replace(LEADING_H1_RE, '')
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E1 (Fact-anchoring): детермінований витяг «анкорів» — конкретних фрагментів
|
|
3
|
-
* з коду, які LLM зобовʼязана згадати в документації, щоб не зісковзнути на
|
|
4
|
-
* generic-фрази.
|
|
5
|
-
*
|
|
6
|
-
* Категорії анкорів:
|
|
7
|
-
* - urls : усі https?://… у вихідному коді
|
|
8
|
-
* - magicStrings : export const X = '…' з непорожнім value (≤120 символів)
|
|
9
|
-
* - errorMarkers : суфікси повідомлень про помилки виду `(rule.mdc)`
|
|
10
|
-
* - configRefs : посилання на .json-конфіги проєкту (.n-cursor.json, …)
|
|
11
|
-
* - examples : ```…```-блоки у file-header JSDoc (першому коментарі файла)
|
|
12
|
-
*
|
|
13
|
-
* Всі регулярки — на сирому src без AST: дешево, безпечно, без false-positive
|
|
14
|
-
* критичної ваги (надмір — менша проблема, ніж пропуск).
|
|
15
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-extract-anchors.md */
|
|
16
2
|
|
|
17
3
|
const URL_RE = /https?:\/\/[^\s'"`)<>]+/g
|
|
18
4
|
// Після обрізання template-частини URL має лишитися host (R10).
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JS-оркестрація генерації файлових док (local-only, ADR 260610-2228).
|
|
3
|
-
*
|
|
4
|
-
* Уся черга/батчинг/CRC-штамп живуть тут, а не в контексті моделі — тому
|
|
5
|
-
* масовий перший прогін на сотні файлів не «заморює» агента. Конвеєр суто
|
|
6
|
-
* локальний: жодних cloud-ескалацій; якщо det-score нижче порогу — дока все
|
|
7
|
-
* одно пишеться з degraded-маркером (`score`/`issues` у frontmatter), а наступний
|
|
8
|
-
* `gen` автоматично доретраює такі доки (один раз на версію джерела — далі `retried:true`).
|
|
9
|
-
*
|
|
10
|
-
* Перед масовим прогоном — health-check omlx: memory-guard зайнятої 8GB машини
|
|
11
|
-
* означає «відклади прогін», а не сотні хибних «✗» у звіті.
|
|
12
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-files-batch.md */
|
|
13
2
|
import { readFileSync, readdirSync, mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs'
|
|
14
3
|
import { basename, dirname, join, relative } from 'node:path'
|
|
15
4
|
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* docgen-judge-measure.mjs — Q4 офлайн-вимірювач (spec 2026-06-14-docgen-judge-design).
|
|
4
|
-
*
|
|
5
|
-
* Міряє false-positive rate детермінованого `scoreDoc`: серед доків, що ПРОЙШЛИ
|
|
6
|
-
* (score ≥ threshold), який % сильна хмарна модель-суддя класифікує як
|
|
7
|
-
* `generic`/`inaccurate`. Це число вирішує, чи будувати рантайм-judge-гейт.
|
|
8
|
-
*
|
|
9
|
-
* Генерація: локальна (N_LOCAL_MIN_MODEL, omlx/* → прямий HTTP) — реальний пайплайн.
|
|
10
|
-
* Суддя: openai-codex/gpt-5.4-mini (сильніша хмара, ніж генератор — інакше вимір беззмістовний).
|
|
11
|
-
* Обидва — через існуючий `../../../lib/llm.mjs callLlm` (маршрутизація за префіксом).
|
|
12
|
-
*
|
|
13
|
-
* Кеш на диску (за хешем контенту) → повторні прогони не регенерують і не пересуджують.
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* node docgen-judge-measure.mjs <file1> <file2> ...
|
|
17
|
-
* MEASURE_CACHE=/tmp/x N_CLOUD_MIN_MODEL=openai-codex/gpt-5.4 node docgen-judge-measure.mjs ...
|
|
18
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-judge-measure.md */
|
|
19
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
20
3
|
import { createHash } from 'node:crypto'
|
|
21
4
|
import { join } from 'node:path'
|
|
@@ -39,10 +22,16 @@ Prefer "inaccurate" over "generic" if any claim is wrong. Respond with ONLY a JS
|
|
|
39
22
|
|
|
40
23
|
const sha = s => createHash('sha256').update(s).digest('hex').slice(0, 16)
|
|
41
24
|
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
42
28
|
function cacheGet(key) {
|
|
43
29
|
const p = join(CACHE_DIR, key + '.json')
|
|
44
30
|
return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : null
|
|
45
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
46
35
|
function cacheSet(key, val) {
|
|
47
36
|
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true })
|
|
48
37
|
writeFileSync(join(CACHE_DIR, key + '.json'), JSON.stringify(val))
|
|
@@ -83,6 +72,9 @@ function judgeCached(src, doc) {
|
|
|
83
72
|
return { ...v, cached: false }
|
|
84
73
|
}
|
|
85
74
|
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
*/
|
|
86
78
|
function main() {
|
|
87
79
|
const files = process.argv.slice(2).filter(f => !f.startsWith('--'))
|
|
88
80
|
if (!files.length) {
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: docgen-crc.mjs
|
|
4
4
|
resource: npm/rules/doc-files/js/docgen-crc.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: cca0a79f
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Детермінований маркер актуальності файлових док: контрольна сума джерела у frontmatter плюс опційний degraded-маркер якості. Єдине джерело правди про «дока свіжа/застаріла/неякісна» для генерації, перевірок і хуків.
|
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Адаптер агрегатора `n-cursor lint` для правила doc-files (opportunistic LLM-fix
|
|
3
|
-
* tier, спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md).
|
|
4
|
-
*
|
|
5
|
-
* Quick-фаза отримує список змінених файлів і мапить їх у пари в **обидва** боки:
|
|
6
|
-
* - змінене **джерело** (`.js/.mjs/.ts/.vue/.py/.rs`) → перевірка його доки `<dir>/docs/<stem>.md`;
|
|
7
|
-
* - змінена/видалена **дока** (`<dir>/docs/<stem>.md`) → перевірка відповідного джерела
|
|
8
|
-
* (той самий stem у каталозі над текою `docs`).
|
|
9
|
-
* Ci-фаза (files === undefined) проганяє повний скан дерева.
|
|
10
|
-
*
|
|
11
|
-
* Детект — `missing` ∪ `crc-mismatch` (детермінований CRC, 0 LLM-токенів); degraded не блокує.
|
|
12
|
-
* Поведінка за осями (правило має `meta.json: llmFix:true`):
|
|
13
|
-
* - `readOnly` (CI/hook): **лише детект** — нуль мутацій/LLM, exit 1 на stale (детермінований гейт);
|
|
14
|
-
* - fix-by-default + omlx **піднято**: opportunistic-генерація stale-доків → re-detect → 0 якщо полагоджено;
|
|
15
|
-
* - fix-by-default + omlx **недоступно**: fix пропущено (повідомлення) + exit 1 — гейт тримається, без false-green.
|
|
16
|
-
*/
|
|
1
|
+
/** @see ./docs/lint.md */
|
|
17
2
|
import { join, dirname, basename, extname } from 'node:path'
|
|
18
3
|
import { existsSync, readdirSync } from 'node:fs'
|
|
19
4
|
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Оркестратор `n-cursor lint` — дві ортогональні осі (spec 2026-06-14-lint-rule-consolidation
|
|
3
|
-
* + компаньйон 2026-06-14-lint-orchestrator-fix-readonly-unification):
|
|
4
|
-
* - **scope** (`--full`): default = дельта vs origin (лише `per-file` правила);
|
|
5
|
-
* `--full` = весь репо (`per-file` ∪ `full` правила);
|
|
6
|
-
* - **behavior** (`--read-only`): default = fix; `--read-only` = лише детект без мутацій.
|
|
7
|
-
*
|
|
8
|
-
* Data-driven: сканує `rules/<id>/meta.json` за полем `lint` (`per-file`|`full`),
|
|
9
|
-
* викликає `rules/<id>/js/lint.mjs` → `lint(files, cwd, { readOnly })`:
|
|
10
|
-
* - default scope: `files` = змінені відносно origin (`collectChangedFilesSince`);
|
|
11
|
-
* - `--full`: `files = undefined` — весь проєкт.
|
|
12
|
-
* Порядок правил — алфавітний. Fail-fast **лише в `--read-only`** (CI/детект): перший
|
|
13
|
-
* ненульовий код спиняє. У fix-режимі (default) ненульовий код НЕ спиняє — проганяємо всі
|
|
14
|
-
* правила й доходимо до кроку виправлення (конформність-драбина), повертаючи найгірший код.
|
|
15
|
-
*/
|
|
1
|
+
/** @see ./docs/orchestrate.md */
|
|
16
2
|
import { existsSync, readdirSync } from 'node:fs'
|
|
17
3
|
import { dirname, join } from 'node:path'
|
|
18
4
|
import { fileURLToPath } from 'node:url'
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* Гейт: валідні слова після дописування у словник зникають; нерозкласифіковані та
|
|
15
15
|
* typo лишаються → cspell повертає !=0 → exit 1 (людина доправляє одруки вручну).
|
|
16
16
|
*/
|
|
17
|
+
import { env } from 'node:process'
|
|
17
18
|
import { spawnSync } from 'node:child_process'
|
|
18
19
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
19
20
|
import { join } from 'node:path'
|
|
@@ -27,7 +28,7 @@ const UNKNOWN_WORD_RE = /Unknown word \(([^)]+)\)/u
|
|
|
27
28
|
const MAX_CLASSIFY_WORDS = 80
|
|
28
29
|
|
|
29
30
|
/** Локальна fix-модель (рішення: єдиний knob `N_LOCAL_MIN_MODEL`). */
|
|
30
|
-
const fixModel = () =>
|
|
31
|
+
const fixModel = () => env.N_LOCAL_MIN_MODEL || ''
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Запускає `cspell .` із захопленням виводу.
|
|
@@ -3,24 +3,30 @@ type: JS Module
|
|
|
3
3
|
title: cspell-fix.mjs
|
|
4
4
|
resource: npm/rules/text/lint/cspell-fix.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 7b40e8f9
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score:
|
|
8
|
+
score: 95
|
|
9
|
+
issues: anchor-miss:meta.json,judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей модуль інтегрує cspell у ланцюжок lint-text для класифікації невідомих слів згідно зі схемою omlx. Він витягує невідомі слова, класифікує їх за допомогою LLM (detect $\rightarrow$ omlx-класифікація) та автоматично дописує валідні терміни до словника `.cspell.json`. Нерозкласифіковані та ймовірні одруки залишаються для ручного рев'ю. Процес є read-only, оскільки він лише класифікує знахідки, а не переписує файли.
|
|
12
16
|
|
|
13
17
|
## Поведінка
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
unknownWords витягує унікальні невідомі слова з виводу cspell.
|
|
20
|
+
appendWordsToDict дописує класифіковані валідні слова до файлу .cspell.json, оновлюючи словник.
|
|
21
|
+
runCspellText запускає cspell, класифікує знахідки за допомогою LLM (якщо увімкнено) та повторно перевіряє код, повертаючи 0 при чистоті або 1 при знахідках.
|
|
17
22
|
|
|
18
23
|
## Публічний API
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
unknownWords — Збирає унікальні слова, які не були знайдені у словнику cspell.
|
|
26
|
+
appendWordsToDict — Додає зібрані слова до файлу `.cspell.json#words` у відсортованому та унікальному вигляді для перегляду у Git.
|
|
27
|
+
runCspellText — Виконує перевірку тексту за допомогою cspell, класифікуючи слова та оновлюючи словник за новою схемою.
|
|
22
28
|
|
|
23
29
|
## Гарантії поведінки
|
|
24
30
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
31
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
32
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
@@ -3,26 +3,27 @@ type: JS Module
|
|
|
3
3
|
title: normalize-cli.mjs
|
|
4
4
|
resource: npm/scripts/lib/adr/normalize-cli.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 63a18347
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 90
|
|
9
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
9
10
|
---
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## Огляд
|
|
13
|
+
|
|
14
|
+
Цей файл є CLI-обгорткою для локального нормалізатора ADR. Він зчитує шляхи до чернеток (`--batch <file>`) та список чистих ADR (`--clean <file>`), використовуючи директорію ADR (`--adr-dir <dir>`) для резолву шляхів. Обгортка запускає `normalizePipeline`, яка генерує JSON-контракт у форматі `{ "operations": [...] }` та виводить його у stdout для подальшого парсингу bash-скриптом. Прогрес та помилки записуються у stderr. Поведінка нормалізатора може бути змінена через змінні середовища: `ADR_NORMALIZE_ALLOW_CLOUD` контролює можливість хмарної ескалації, а `ADR_NORMALIZE_VOTES` визначає кількість голосів self-consistency для чистих ADR.
|
|
12
15
|
|
|
13
16
|
## Поведінка
|
|
14
17
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
20
|
-
6.
|
|
21
|
-
7.
|
|
22
|
-
8.
|
|
23
|
-
9.
|
|
24
|
-
10. `runAdrNormalizeLocalCli` друкує JSON-об'єкт, що містить операції, у stdout.
|
|
25
|
-
11. `runAdrNormalizeLocalCli` завершує роботу з кодом успіху.
|
|
18
|
+
1. Викликати runAdrNormalizeLocalCli.
|
|
19
|
+
2. Зчитувати список шляхів до чернеток батчу з файлу, вказаного через аргумент `--batch`.
|
|
20
|
+
3. Зчитувати список імен чистих ADR (кандидатів до злиття) з файлу, вказаного через аргумент `--clean`, якщо він наданий.
|
|
21
|
+
4. Визначати директорію ADR, використовуючи аргумент `--adr-dir` або за замовчуванням `cwd/docs/adr`.
|
|
22
|
+
5. Зчитувати вміст кожної чернетки батчу, резолвя шляхи відносно `--adr-dir`.
|
|
23
|
+
6. Зчитувати значення змінних середовища `ADR_NORMALIZE_ALLOW_CLOUD` та `ADR_NORMALIZE_VOTES`.
|
|
24
|
+
7. Викликати внутрішній механізм нормалізації, передаючи зібрані чернетки, список чистих ADR, та конфігурацію, отриману з змінних середовища.
|
|
25
|
+
8. Виводити інформацію про прогрес та статистику в stderr.
|
|
26
|
+
9. Виводити JSON-об'єкт, що містить операції, у stdout.
|
|
26
27
|
|
|
27
28
|
## Публічний API
|
|
28
29
|
|
|
@@ -30,5 +31,4 @@ runAdrNormalizeLocalCli — запускає субкоманду, виводя
|
|
|
30
31
|
|
|
31
32
|
## Гарантії поведінки
|
|
32
33
|
|
|
33
|
-
- Read-only:
|
|
34
|
-
- Не звертається до мережі.
|
|
34
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* ADR_NORMALIZE_ALLOW_CLOUD=1 дозволити хмарну ескалацію tier-каскаду (default off)
|
|
17
17
|
* ADR_NORMALIZE_VOTES=N голосів self-consistency для clean-ребер (default 2)
|
|
18
18
|
*/
|
|
19
|
+
import { env } from 'node:process'
|
|
19
20
|
import { readFileSync } from 'node:fs'
|
|
20
21
|
import { basename, isAbsolute, join } from 'node:path'
|
|
21
22
|
import { normalizePipeline } from './normalize-pipeline.mjs'
|
|
@@ -57,8 +58,8 @@ export function runAdrNormalizeLocalCli(argv) {
|
|
|
57
58
|
})
|
|
58
59
|
const cleanList = args.clean ? readLines(args.clean).map((c) => basename(c)) : []
|
|
59
60
|
|
|
60
|
-
const allowCloud =
|
|
61
|
-
const votes = Number(
|
|
61
|
+
const allowCloud = env.ADR_NORMALIZE_ALLOW_CLOUD === '1'
|
|
62
|
+
const votes = Number(env.ADR_NORMALIZE_VOTES) || 2
|
|
62
63
|
|
|
63
64
|
const { operations, stats, trace } = normalizePipeline(drafts, cleanList, {
|
|
64
65
|
allowCloud,
|
|
@@ -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
|
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Re-export спільного списку ignore-глобів із правила doc-files.
|
|
3
|
-
*
|
|
4
|
-
* Канонічне джерело — `npm/rules/doc-files/js/docgen-ignore.mjs`: скіл doc-aggregate
|
|
5
|
-
* і правило doc-files мусять бачити однакове дерево кодових файлів, інакше агрегат
|
|
6
|
-
* посилатиметься на файли без док (або навпаки). Залежність спрямована
|
|
7
|
-
* doc-aggregate → doc-files за ADR про розбиття docgen.
|
|
8
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-ignore.md */
|
|
9
2
|
export * from '../../../rules/doc-files/js/docgen-ignore.mjs'
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: docgen-ignore.mjs
|
|
4
4
|
resource: npm/skills/doc-aggregate/js/docgen-ignore.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 3b579230
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Re-export спільного списку ignore-глобів зі скіла doc-files: обидва скіли документації (пофайлові доки й агрегати) мусять бачити однакове дерево кодових файлів, інакше агрегат посилатиметься на файли без док або навпаки.
|