@nitra/cursor 12.15.1 → 12.16.1

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/n-cursor.js +1 -1
  3. package/lib/docs/index.md +9 -6
  4. package/lib/docs/pi-agent-fix.md +28 -0
  5. package/lib/docs/pi-agent-skill.md +36 -0
  6. package/lib/docs/pi-model-tiers.md +46 -0
  7. package/lib/docs/pi-one-shot.md +34 -0
  8. package/lib/docs/pi-telemetry-store.md +33 -0
  9. package/lib/docs/pi-trace.md +27 -0
  10. package/lib/docs/pi-write-guard.md +32 -0
  11. package/lib/pi-agent-fix.mjs +253 -0
  12. package/lib/pi-agent-skill.mjs +181 -0
  13. package/lib/pi-model-tiers.mjs +109 -0
  14. package/lib/pi-one-shot.mjs +129 -0
  15. package/lib/pi-telemetry-store.mjs +0 -0
  16. package/lib/pi-trace.mjs +40 -0
  17. package/lib/pi-write-guard.mjs +147 -0
  18. package/package.json +5 -1
  19. package/rules/doc-files/js/docgen-files-batch.mjs +20 -5
  20. package/rules/doc-files/js/docgen-gen.mjs +42 -25
  21. package/rules/doc-files/js/docgen-judge-measure.mjs +16 -13
  22. package/rules/doc-files/js/docgen-judge.mjs +11 -9
  23. package/rules/doc-files/js/docs/docgen-files-batch.md +3 -20
  24. package/rules/doc-files/js/docs/docgen-gen.md +3 -20
  25. package/rules/doc-files/js/docs/docgen-judge-measure.md +3 -18
  26. package/rules/doc-files/js/docs/docgen-judge.md +3 -22
  27. package/rules/npm-module/js/docs/skill_meta.md +22 -15
  28. package/rules/npm-module/js/skill_meta.mjs +5 -1
  29. package/rules/text/js/cspell-fix.mjs +15 -16
  30. package/rules/text/js/docs/cspell-fix.md +16 -9
  31. package/rules/text/main.mjs +4 -4
  32. package/schemas/skill-meta.json +8 -0
  33. package/scripts/docs/skills-cli.md +21 -25
  34. package/scripts/lib/adr/docs/normalize-cli.md +3 -20
  35. package/scripts/lib/adr/docs/normalize-pipeline.md +3 -33
  36. package/scripts/lib/adr/normalize-cli.mjs +2 -2
  37. package/scripts/lib/adr/normalize-pipeline.mjs +78 -44
  38. package/scripts/lib/docs/skill-meta.md +27 -10
  39. package/scripts/lib/fix/docs/escalation-log.md +10 -9
  40. package/scripts/lib/fix/docs/orchestrator.md +13 -20
  41. package/scripts/lib/fix/escalation-log.mjs +1 -1
  42. package/scripts/lib/fix/orchestrator.mjs +65 -31
  43. package/scripts/lib/skill-meta.mjs +22 -0
  44. package/scripts/skills-cli.mjs +52 -14
  45. package/scripts/utils/ast-extract.mjs +105 -0
  46. package/scripts/utils/docs/ast-extract.md +30 -0
  47. package/lib/docs/llm.md +0 -33
  48. package/lib/docs/models.md +0 -48
  49. package/lib/docs/omlx-trace.md +0 -49
  50. package/lib/docs/omlx.md +0 -41
  51. package/lib/llm.mjs +0 -215
  52. package/lib/models.mjs +0 -75
  53. package/lib/omlx-trace.mjs +0 -158
  54. package/lib/omlx.mjs +0 -220
  55. package/scripts/lib/fix/docs/llm-fix-apply.md +0 -31
  56. package/scripts/lib/fix/docs/llm-lint-fix.md +0 -31
  57. package/scripts/lib/fix/docs/llm-worker.md +0 -28
  58. package/scripts/lib/fix/docs/verbose-block.md +0 -27
  59. package/scripts/lib/fix/llm-fix-apply.mjs +0 -113
  60. package/scripts/lib/fix/llm-lint-fix.mjs +0 -82
  61. package/scripts/lib/fix/llm-worker.mjs +0 -346
  62. package/scripts/lib/fix/verbose-block.mjs +0 -82
@@ -110,9 +110,9 @@ function preflight(dep) {
110
110
  * Внутрішні кроки `lint-text` без локу.
111
111
  * @param {boolean} [readOnly] true → лише детект без авто-фіксу (нуль мутацій — CI/pre-commit)
112
112
  * @param {boolean} [llmFix] opt-in omlx-класифікація cspell (інші кроки фіксяться детерміновано за readOnly)
113
- * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
113
+ * @returns {Promise<number>} 0 — все OK, інакше — код першого кроку, що впав
114
114
  */
115
- function runLintTextSteps(readOnly = false, llmFix = false) {
115
+ async function runLintTextSteps(readOnly = false, llmFix = false) {
116
116
  // Auto-install: throws on failure → propagates as exit 1 from runStandardLint
117
117
  ensureTool('shellcheck')
118
118
  ensureTool('dotenv-linter')
@@ -120,8 +120,8 @@ function runLintTextSteps(readOnly = false, llmFix = false) {
120
120
  // patch потрібен лише для авто-фіксу shellcheck; у read-only пропускаємо preflight.
121
121
  if (!readOnly && !preflight(PATCH_PREFLIGHT)) return 1
122
122
 
123
- console.log(`\n▶ cspell (${!readOnly && llmFix ? 'omlx-класифікація + словник + перевірка' : 'перевірка'})`)
124
- const cspellCode = runCspellText(process.cwd(), readOnly, llmFix)
123
+ console.log(`\n▶ cspell (${!readOnly && llmFix ? 'LLM-класифікація + словник + перевірка' : 'перевірка'})`)
124
+ const cspellCode = await runCspellText(process.cwd(), readOnly, llmFix)
125
125
  if (cspellCode !== 0) return cspellCode
126
126
 
127
127
  console.log(`\n▶ shellcheck (${readOnly ? 'перевірка' : 'авто-фікс + фінальна перевірка'} *.sh)`)
@@ -17,6 +17,14 @@
17
17
  "worktree": {
18
18
  "type": "boolean",
19
19
  "description": "true — виконувати скіл в окремому git-worktree, один інстанс за раз (без паралельного запуску); false — у worktree не виконується."
20
+ },
21
+ "requireRoot": {
22
+ "type": "boolean",
23
+ "description": "true — скіл мутує CWD без worktree-ізоляції і має стартувати з кореня репо. Для worktree:true поле зайве (корінь гарантує worktree)."
24
+ },
25
+ "tier": {
26
+ "description": "Абстрактна тира моделі (capability, НЕ провайдер) для агентного виконання скіла через pi-runner: кожна каскадить local→cloud (напр. avg → LOCAL_AVG → LOCAL_MAX → CLOUD_AVG), тож avg ≠ \"локальна\". За відсутності — \"max\" (відкриті скіли потребують сильної моделі).",
27
+ "enum": ["min", "avg", "max"]
20
28
  }
21
29
  }
22
30
  }
@@ -3,40 +3,36 @@ type: JS Module
3
3
  title: skills-cli.mjs
4
4
  resource: npm/scripts/skills-cli.mjs
5
5
  docgen:
6
- crc: 85da573a
6
+ crc: e2d5fac6
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
7
8
  score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
8
11
  ---
9
12
 
10
- Файл надає інструменти для запуску скілів пакета `@nitra/cursor` без синку правил у проєкт. Він зчитує інструкції скілів з файлів `SKILL.md` та збирає контекст з `package.json`, `tsconfig.json` та `.n-cursor.json` для формування промпту. Зібраний промпт делегується в `cursor-agent` або `claude` для виконання необхідних дій.
13
+ ## Огляд
11
14
 
12
- ## Поведінка
13
-
14
- normalizeSkillId
15
- Перетворює ім'я скілу на ідентифікатор, видаляючи префікс n-
16
-
17
- listSkillIds
18
- Отримує відсортований список ідентифікаторів скілів, що мають файл SKILL.md
15
+ Цей модуль забезпечує керування функціоналом скілів пакета `@nitra/cursor`. Він каталогізує доступні скіли на основі наявності файлу `SKILL.md` у відповідному пакеті. Для виконання скілу збирається контекст, що включає інструкції скілу та інформацію з конфігураційних файлів проєкту: `package.json`, `tsconfig.json`, `.n-cursor.json` та `main.json`. Система дозволяє або вивести список доступних скілів (`npx @nitra/cursor skill list`), або ініціювати виконання обраного скілу (наприклад, `npx @nitra/cursor skill pi taze`), з можливістю передачі додаткових аргументів. Пріоритетним механізмом виконання є інтеграція з вбудованим pi-агентом, тоді як для забезпечення сумісності підтримуються застарілі виклики через зовнішні CLI (`cursor`, `claude`).
19
16
 
20
- buildSkillPrompt
21
- Створює промпт, збираючи інструкцію скілу та контекст поточного проєкту з файлів package.json, tsconfig.json та .n-cursor.json
22
-
23
- resolveBundledPackageRoot
24
- Визначає абсолютний шлях до кореня пакета з урахуванням модуля
17
+ ## Поведінка
25
18
 
26
- runSkillsCli
27
- Запускає CLI для виконання скілів, збираючи промпт та контекст проєкту, та делегує виконання в `cursor-agent` або `claude`
19
+ Поведінка
20
+ normalizeSkillId знімає префікс `n-` з імені скілу для приведення його до стандартного ID.
21
+ listSkillIds отримує відсортований список ID скілів, які мають файл `SKILL.md` у вказаній директорії скілів.
22
+ buildSkillPrompt збирає комплексний промпт для виконання скілу, об'єднуючи інструкцію скілу з конфігураційними файлами проєкту, такими як `package.json`, `tsconfig.json` та `.n-cursor.json`.
23
+ resolveBundledPackageRoot визначає абсолютний шлях до кореня пакету `@nitra/cursor`, використовуючи інформацію про поточний модуль.
24
+ runSkillsCli виконує логіку командного інтерфейсу для керування скілами: може вивести список доступних скілів, зібрати промпт для скілу або ініціювати його виконання через вбудований pi-агент, використовуючи зовнішні CLI як резервний варіант.
28
25
 
29
26
  ## Публічний API
30
27
 
31
- normalizeSkillId — перетворює ідентифікатор навички.
32
- listSkillIds — отримує список ідентифікаторів навичок.
33
- buildSkillPrompt — створює запит для навички.
34
- resolveBundledPackageRoot — визначає основний каталог пакета.
35
- runSkillsCli — запускає клієнт для навичок.
28
+ - normalizeSkillId — знімає префікс `n-` з імені скілу, приводячи його до id каталогу в пакеті.
29
+ - listSkillIds — повертає відсортований список id скілів, що мають `SKILL.md`.
30
+ - buildSkillPrompt — збирає промпт виконання: інструкція скілу + контекст проєкту (`package.json`, `tsconfig.json`, `.n-cursor.json`); кидає, якщо скіл невідомий.
31
+ - resolveBundledPackageRoot — абсолютний шлях до кореня встановленого пакета `@nitra/cursor`.
32
+ - runSkillsCli — асинхронний entrypoint підкоманди `skill`: `list`, друк промпта на stdout, або виконання через `pi` (рекомендовано), `cursor`/`claude` (deprecated). Повертає exit-код.
36
33
 
37
34
  ## Гарантії поведінки
38
35
 
39
- - Read-only: файл не виконує операцій запису у файлову систему.
40
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
41
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
42
- - Не звертається до мережі.
36
+ - Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішнім `cursor`/`claude` CLI.
37
+ - Тира моделі для `pi`-runner береться з `main.json.tier` скіла (дефолт `max`).
38
+ - `cursor`/`claude` deprecated: друкують попередження й лишаються як fallback, доки не налаштовано pi-модель.
@@ -3,29 +3,12 @@ type: JS Module
3
3
  title: normalize-cli.mjs
4
4
  resource: npm/scripts/lib/adr/normalize-cli.mjs
5
5
  docgen:
6
- crc: 63a18347
6
+ crc: 222bfca4
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 55
9
+ issues: no-overview,short-behavior,best-of-2:retry-lost
9
10
  ---
10
11
 
11
- Цей файл є 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
-
13
- ## Поведінка
14
-
15
- 1. Викликати runAdrNormalizeLocalCli.
16
- 2. Зчитувати список шляхів до чернеток батчу з файлу, вказаного через аргумент `--batch`.
17
- 3. Зчитувати список імен чистих ADR (кандидатів до злиття) з файлу, вказаного через аргумент `--clean`, якщо він наданий.
18
- 4. Визначати директорію ADR, використовуючи аргумент `--adr-dir` або за замовчуванням `cwd/docs/adr`.
19
- 5. Зчитувати вміст кожної чернетки батчу, резолвя шляхи відносно `--adr-dir`.
20
- 6. Зчитувати значення змінних середовища `ADR_NORMALIZE_ALLOW_CLOUD` та `ADR_NORMALIZE_VOTES`.
21
- 7. Викликати внутрішній механізм нормалізації, передаючи зібрані чернетки, список чистих ADR, та конфігурацію, отриману з змінних середовища.
22
- 8. Виводити інформацію про прогрес та статистику в stderr.
23
- 9. Виводити JSON-об'єкт, що містить операції, у stdout.
24
-
25
- ## Публічний API
26
-
27
- runAdrNormalizeLocalCli — запускає субкоманду, виводячи JSON операцій у стандартний вивід та прогрес у стандартний помилковий вивід.
28
-
29
12
  ## Гарантії поведінки
30
13
 
31
14
  - Read-only: не виконує операцій запису (ФС/БД).
@@ -3,42 +3,12 @@ type: JS Module
3
3
  title: normalize-pipeline.mjs
4
4
  resource: npm/scripts/lib/adr/normalize-pipeline.mjs
5
5
  docgen:
6
- crc: 9f2d42d3
6
+ crc: 8e9ca76f
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- issues: judge:inaccurate:0.99
8
+ score: 55
9
+ issues: no-overview,short-behavior,best-of-2:retry-lost
10
10
  ---
11
11
 
12
- Файл реалізує локально-орієнтований конвеєр для нормалізації чернеток архітектурних рішень (ADR). Він працює за принципом інверсії керування: JavaScript оркеструє процес, а LLM відповідає лише на вузькі, верифіковані запитання, будучи заточеним під малу локальну модель (omlx/gemma-4b). LLM використовується лише для бінарного судження схожості між записами (наприклад, через `edge-judge`) та витягування змісту секцій у JSON (через `gen-MADR`). Конвеєр збирає, валідує та складає повний MADR-каркас, використовуючи кластеризацію (`cluster` та `union-find`) та проходить через `validation gate`, повертаючи результат у вигляді операцій для застосування.
13
-
14
- ## Поведінка
15
-
16
- tokenize: Токенізує назву або слаг, видаляючи стоп-слова та розширюючи його на значущі токени.
17
- jaccard: Обчислює коефіцієнт Jaccard між двома множинами токенів.
18
- draftTitle: Витягує заголовок з тіла чернетки, використовуючи ADR-шаблон або перший не-MADR заголовок.
19
- isNoDecision: Детерміновано визначає, чи рішення в чернетці явно не прийняте.
20
- buildEdges: Будує кандидати-ребра між чернетками та між чернетками і чистими ADR-файлами на основі лексичної схожості.
21
- validateMadr: Перевіряє згенерований MADR-текст на відповідність вимогам OKF та структурним елементам.
22
- madrDate: Детерміновано визначає ISO-дату для поля **Date:**, використовуючи дані з чернетки або імені файлу.
23
- normalizeSections: Нормалізує сирий JSON-вивід LLM у строгу структуру секцій MADR, толерантно до дрібних відхилень моделі.
24
- assembleMadr: Збирає канонічний MADR-markdown, використовуючи заголовок, дату та нормалізований контент секцій.
25
- genMadr: Витягує зміст архітектурного рішення з чернетки у JSON, а потім збирає його у валідний MADR-текст.
26
- normalizePipeline: Виконує повний конвеєр нормалізації, кластеризуючи чернетки та генеруючи операції для застосування.
27
-
28
- ## Публічний API
29
-
30
- tokenize — Розбиває назву чи слаг на значущі слова, ігноруючи стоп-слова.
31
- jaccard — Вимірює схожість двох наборів слів.
32
- draftTitle — Витягує заголовок з чернетки, надаючи пріоритет заголовку ADR.
33
- isNoDecision — Визначає, чи не було прийнято рішення у чернетці, щоб уникнути створення зайвих ADR.
34
- buildEdges — Створює потенційні зв'язки між елементами на основі схожості слів.
35
- validateMadr — Перевіряє якість згенерованого документа ADR.
36
- madrDate — Формує стандартизовану дату для документа, використовуючи метадані або ім'я файлу.
37
- normalizeSections — Приводить неструктурований вивід генеративної моделі до чіткого формату секцій.
38
- assembleMadr — Збирає повний, стандартизований документ ADR, використовуючи фіксовані шаблони.
39
- genMadr — Створює чернетку документа ADR на основі вхідних даних.
40
- normalizePipeline — Виконує повний потік обробки даних, повертаючи результати та статистику.
41
-
42
12
  ## Гарантії поведінки
43
13
 
44
14
  - (специфічних машинно-виведених гарантій немає)
@@ -43,7 +43,7 @@ const readLines = (file) =>
43
43
  * @param {string[]} argv аргументи після назви команди
44
44
  * @returns {number} exit-code (0 — успіх, 1 — помилка вводу)
45
45
  */
46
- export function runAdrNormalizeLocalCli(argv) {
46
+ export async function runAdrNormalizeLocalCli(argv) {
47
47
  const args = parseArgs(argv)
48
48
  const adrDir = args['adr-dir'] ?? join(process.cwd(), 'docs/adr')
49
49
  if (!args.batch) {
@@ -61,7 +61,7 @@ export function runAdrNormalizeLocalCli(argv) {
61
61
  const allowCloud = env.ADR_NORMALIZE_ALLOW_CLOUD === '1'
62
62
  const votes = Number(env.ADR_NORMALIZE_VOTES) || 2
63
63
 
64
- const { operations, stats, trace } = normalizePipeline(drafts, cleanList, {
64
+ const { operations, stats, trace } = await normalizePipeline(drafts, cleanList, {
65
65
  allowCloud,
66
66
  votes,
67
67
  onProgress: (m) => console.error(`adr-normalize-local: ${m}`)
@@ -24,8 +24,8 @@
24
24
  * Повертає той самий operations[]-контракт, що й single-shot — apply-логіка спільна.
25
25
  */
26
26
  import { z } from 'zod'
27
- import { callLlm, classifyOmlxError } from '../../../lib/llm.mjs'
28
- import { CLOUD_MIN, resolveModel } from '../../../lib/models.mjs'
27
+ import { runOneShot } from '../../../lib/pi-one-shot.mjs'
28
+ import { CLOUD_MIN, resolveModel } from '../../../lib/pi-model-tiers.mjs'
29
29
 
30
30
  // ─────────────────────────── Stage 0: retrieval (JS) ───────────────────────────
31
31
 
@@ -168,36 +168,44 @@ const LOCAL = () => resolveModel('min')
168
168
  * @param {Array<{role:string,content:string}>} messages чат-повідомлення для LLM
169
169
  * @param {(raw:string)=>any} parse валідатор (кидає на невалідному)
170
170
  * @param {{label:string, allowCloud:boolean, attempts?:number, stats:object, maxTokens?:number}} cfg конфіг каскаду (мітка, дозвіл на хмару, спроби, лічильники, ліміт токенів)
171
- * @returns {any} результат parse
171
+ * @returns {Promise<any>} результат parse
172
172
  * @throws {Error} якщо всі спроби провалені
173
173
  */
174
- function callWithCascade(messages, parse, cfg) {
174
+ async function callWithCascade(messages, parse, cfg) {
175
175
  const attempts = cfg.attempts ?? 2
176
- const temps = [0.1, 0.4, 0.7]
177
176
  let lastErr = null
178
177
  for (let a = 0; a < attempts; a++) {
179
- try {
180
- cfg.stats.localCalls++
181
- const raw = callLlm(messages, LOCAL(), {
182
- timeoutMs: 120_000,
183
- temperature: temps[a] ?? 0.2,
184
- maxTokens: cfg.maxTokens ?? 4096,
185
- caller: `adr-pipe:${cfg.label}`
186
- })
187
- return parse(raw)
188
- } catch (error) {
189
- lastErr = error
190
- if (classifyOmlxError(error.message) === 'infra') break
178
+ cfg.stats.localCalls++
179
+ const res = await runOneShot({ messages, modelSpec: LOCAL(), timeoutMs: 120_000, caller: `adr-pipe:${cfg.label}` })
180
+ if (!res.error) {
181
+ try {
182
+ return parse(res.content)
183
+ } catch (error) {
184
+ lastErr = error // невалідний вихід → наступна спроба
185
+ continue
186
+ }
191
187
  }
188
+ lastErr = new Error(res.error)
189
+ // infra (registry/session/модель недоступна) → ретрай локально марний.
190
+ if (/registry:|session:|не знайдена/i.test(res.error)) break
192
191
  }
193
192
  if (cfg.allowCloud && CLOUD_MIN) {
194
- try {
195
- cfg.stats.cloudCalls++
196
- cfg.stats.escalations++
197
- const raw = callLlm(messages, CLOUD_MIN, { timeoutMs: 120_000, temperature: 0.2, maxTokens: cfg.maxTokens ?? 4096, caller: `adr-pipe:${cfg.label}:cloud` })
198
- return parse(raw)
199
- } catch (error) {
200
- lastErr = error
193
+ cfg.stats.cloudCalls++
194
+ cfg.stats.escalations++
195
+ const res = await runOneShot({
196
+ messages,
197
+ modelSpec: CLOUD_MIN,
198
+ timeoutMs: 120_000,
199
+ caller: `adr-pipe:${cfg.label}:cloud`
200
+ })
201
+ if (!res.error) {
202
+ try {
203
+ return parse(res.content)
204
+ } catch (error) {
205
+ lastErr = error
206
+ }
207
+ } else {
208
+ lastErr = new Error(res.error)
201
209
  }
202
210
  }
203
211
  cfg.stats.failures++
@@ -244,20 +252,27 @@ same=true ЛИШЕ якщо це по суті одне рішення (дубл
244
252
  * @param {{votes?:number, minConf?:number}} [vote] override голосів і порога на тип ребра
245
253
  * @returns {{same:boolean, votes:object[]}} підтвердження same та сирі голоси
246
254
  */
247
- function judgeEdge(aTitle, aBody, bTitle, bBody, cfg, vote = {}) {
255
+ async function judgeEdge(aTitle, aBody, bTitle, bBody, cfg, vote = {}) {
248
256
  const nVotes = vote.votes ?? cfg.votes ?? 2
249
257
  const minConf = vote.minConf ?? 0.5
250
258
  const user = `Запис A — "${aTitle}":\n${aBody.slice(0, 1500)}\n\n---\n\nЗапис B — "${bTitle}":\n${bBody.slice(0, 1500)}\n\nЦе одне й те саме рішення?`
251
- const parse = (raw) => EdgeSchema.parse(extractJson(raw))
259
+ const parse = raw => EdgeSchema.parse(extractJson(raw))
252
260
  const votes = []
253
261
  for (let v = 0; v < nVotes; v++) {
254
262
  try {
255
- votes.push(callWithCascade([{ role: 'system', content: EDGE_SYS }, { role: 'user', content: user }], parse, { label: 'edge', allowCloud: cfg.allowCloud, stats: cfg.stats, maxTokens: 300 }))
263
+ votes.push(
264
+ await callWithCascade([{ role: 'system', content: EDGE_SYS }, { role: 'user', content: user }], parse, {
265
+ label: 'edge',
266
+ allowCloud: cfg.allowCloud,
267
+ stats: cfg.stats,
268
+ maxTokens: 300
269
+ })
270
+ )
256
271
  } catch {
257
272
  votes.push({ same: false, confidence: 0, reason: 'judge failed → conservative different' })
258
273
  }
259
274
  }
260
- const sameCount = votes.filter((v) => v.same && v.confidence >= minConf).length
275
+ const sameCount = votes.filter(v => v.same && v.confidence >= minConf).length
261
276
  return { same: sameCount === votes.length, votes }
262
277
  }
263
278
 
@@ -275,11 +290,16 @@ const KIND_SYS = `Ти оцінюєш чернетку архітектурно
275
290
  Поверни ЛИШЕ JSON: { "kind": "standalone"|"trivial", "reason": "<коротко українською>" }
276
291
  Якщо сумніваєшся — "standalone" (краще зберегти).`
277
292
 
278
- function judgeKind(title, body, cfg) {
293
+ async function judgeKind(title, body, cfg) {
279
294
  const user = `Чернетка — "${title}":\n${body.slice(0, 2500)}\n\nstandalone чи trivial?`
280
- const parse = (raw) => KindSchema.parse(extractJson(raw))
295
+ const parse = raw => KindSchema.parse(extractJson(raw))
281
296
  try {
282
- return callWithCascade([{ role: 'system', content: KIND_SYS }, { role: 'user', content: user }], parse, { label: 'kind', allowCloud: cfg.allowCloud, stats: cfg.stats, maxTokens: 200 })
297
+ return await callWithCascade([{ role: 'system', content: KIND_SYS }, { role: 'user', content: user }], parse, {
298
+ label: 'kind',
299
+ allowCloud: cfg.allowCloud,
300
+ stats: cfg.stats,
301
+ maxTokens: 200
302
+ })
283
303
  } catch {
284
304
  return { kind: 'standalone', reason: 'judge failed → conservative standalone' }
285
305
  }
@@ -424,20 +444,28 @@ export function assembleMadr({ title, date, sections: s }) {
424
444
  ].join('\n')
425
445
  }
426
446
 
427
- export function genMadr(title, body, captured, cfg, file = '') {
447
+ export async function genMadr(title, body, captured, cfg, file = '') {
428
448
  const date = madrDate(captured, file)
429
449
  const slug = slugify(title)
430
450
  const user = `Чернетка "${title}":\n\n${body.slice(0, 4000)}\n\nВитягни зміст рішення у JSON.`
431
- const parse = (raw) => {
451
+ const parse = raw => {
432
452
  const sections = normalizeSections(extractJson(raw))
433
- if (!sections.context && !sections.chosen && !sections.rationale) throw new Error('empty extraction (no context/decision)')
453
+ if (!sections.context && !sections.chosen && !sections.rationale) {
454
+ throw new Error('empty extraction (no context/decision)')
455
+ }
434
456
  const content = assembleMadr({ title, date, sections })
435
457
  const v = validateMadr(content)
436
458
  if (!v.ok) throw new Error(`MADR invalid: ${v.errors.join('; ')}`)
437
459
  return content
438
460
  }
439
461
  try {
440
- const content = callWithCascade([{ role: 'system', content: GEN_SYS }, { role: 'user', content: user }], parse, { label: 'gen', allowCloud: cfg.allowCloud, stats: cfg.stats, attempts: 3, maxTokens: 2048 })
462
+ const content = await callWithCascade([{ role: 'system', content: GEN_SYS }, { role: 'user', content: user }], parse, {
463
+ label: 'gen',
464
+ allowCloud: cfg.allowCloud,
465
+ stats: cfg.stats,
466
+ attempts: 3,
467
+ maxTokens: 2048
468
+ })
441
469
  return { content, slug, valid: true }
442
470
  } catch (error) {
443
471
  cfg.stats.madrInvalid++
@@ -451,7 +479,7 @@ export function genMadr(title, body, captured, cfg, file = '') {
451
479
  // новий зміст-прозу; заголовок із детермінованою датою додає genMerge.
452
480
  const MERGE_SYS = `Ти готуєш короткий додаток до існуючого ADR. Напиши ЛИШЕ новий зміст (проза/bullets), якого ще НЕМА в цільовому ADR — уточнення/виправлення/продовження. Стисло, українською, без заголовків, без code-fence, без передмови.`
453
481
 
454
- function genMerge(title, body, captured, targetTitle, cfg, file = '') {
482
+ async function genMerge(title, body, captured, targetTitle, cfg, file = '') {
455
483
  const date = madrDate(captured, file)
456
484
  const user = `Цільовий ADR: "${targetTitle}".\nЧернетка-доповнення "${title}" (${date}):\n${body.slice(0, 2500)}\n\nЛише новий зміст, без заголовка.`
457
485
  const head = `## Update ${date}`
@@ -464,7 +492,13 @@ function genMerge(title, body, captured, targetTitle, cfg, file = '') {
464
492
  return `${head}\n\n${cleaned}`
465
493
  }
466
494
  try {
467
- return callWithCascade([{ role: 'system', content: MERGE_SYS }, { role: 'user', content: user }], parse, { label: 'merge', allowCloud: cfg.allowCloud, stats: cfg.stats, attempts: 2, maxTokens: 1500 })
495
+ return await callWithCascade([{ role: 'system', content: MERGE_SYS }, { role: 'user', content: user }], parse, {
496
+ label: 'merge',
497
+ allowCloud: cfg.allowCloud,
498
+ stats: cfg.stats,
499
+ attempts: 2,
500
+ maxTokens: 1500
501
+ })
468
502
  } catch {
469
503
  return `${head}\n\n(доповнення з чернетки "${title}")`
470
504
  }
@@ -498,7 +532,7 @@ const noop = () => {
498
532
  * @param {{allowCloud?:boolean, votes?:number, onProgress?:(m:string)=>void}} [opts] хмарна ескалація, кількість голосів і колбек прогресу
499
533
  * @returns {{operations:object[], stats:object, trace:object}} операції apply-ops, лічильники та діагностичний trace
500
534
  */
501
- export function normalizePipeline(drafts, cleanList, opts = {}) {
535
+ export async function normalizePipeline(drafts, cleanList, opts = {}) {
502
536
  const allowCloud = opts.allowCloud ?? false
503
537
  const log = opts.onProgress ?? noop
504
538
  const stats = { localCalls: 0, cloudCalls: 0, escalations: 0, failures: 0, madrInvalid: 0 }
@@ -522,7 +556,7 @@ export function normalizePipeline(drafts, cleanList, opts = {}) {
522
556
  const dsu = makeDSU(drafts.length)
523
557
  const confirmedDD = []
524
558
  for (const [i, j] of dd) {
525
- const r = judgeEdge(titles[i], drafts[i].body, titles[j], drafts[j].body, cfg, { votes: 3, minConf: 0.6 })
559
+ const r = await judgeEdge(titles[i], drafts[i].body, titles[j], drafts[j].body, cfg, { votes: 3, minConf: 0.6 })
526
560
  if (r.same) { dsu.union(i, j); confirmedDD.push([i, j]) }
527
561
  }
528
562
  log(`edge-judge: ${confirmedDD.length}/${dd.length} draft-draft ребер підтверджено`)
@@ -534,7 +568,7 @@ export function normalizePipeline(drafts, cleanList, opts = {}) {
534
568
  for (const [i, cands] of dcByDraft) {
535
569
  for (const c of cands) {
536
570
  const cTitle = stripAdrName(c)
537
- const r = judgeEdge(titles[i], drafts[i].body, cTitle, cTitle, cfg)
571
+ const r = await judgeEdge(titles[i], drafts[i].body, cTitle, cTitle, cfg)
538
572
  if (r.same) { cleanTarget[i] = c; break }
539
573
  }
540
574
  }
@@ -578,7 +612,7 @@ export function normalizePipeline(drafts, cleanList, opts = {}) {
578
612
  // одинаки без clean-target → kind-judge
579
613
  for (let i = 0; i < drafts.length; i++) {
580
614
  if (decision[i].op === 'kind') {
581
- const k = judgeKind(titles[i], drafts[i].body, cfg)
615
+ const k = await judgeKind(titles[i], drafts[i].body, cfg)
582
616
  decision[i] = k.kind === 'trivial' ? { op: 'delete', reason: k.reason } : { op: 'rewrite' }
583
617
  }
584
618
  }
@@ -587,7 +621,7 @@ export function normalizePipeline(drafts, cleanList, opts = {}) {
587
621
  const slugByIdx = Array.from({ length: drafts.length }).fill(null)
588
622
  for (let i = 0; i < drafts.length; i++) {
589
623
  if (decision[i].op !== 'rewrite') continue
590
- const g = genMadr(titles[i], drafts[i].body, captured[i], cfg, drafts[i].file)
624
+ const g = await genMadr(titles[i], drafts[i].body, captured[i], cfg, drafts[i].file)
591
625
  slugByIdx[i] = g.slug
592
626
  if (g.valid) {
593
627
  operations.push({ op: 'rewrite', file: drafts[i].file, slug: g.slug, content: g.content })
@@ -603,11 +637,11 @@ export function normalizePipeline(drafts, cleanList, opts = {}) {
603
637
  if (d.op === 'merge-anchor') {
604
638
  const slug = slugByIdx[d.anchorIdx]
605
639
  if (!slug) { log(`merge-anchor ${drafts[i].file}: anchor gen failed → skip`); continue }
606
- const add = genMerge(titles[i], drafts[i].body, captured[i], titles[d.anchorIdx], cfg, drafts[i].file)
640
+ const add = await genMerge(titles[i], drafts[i].body, captured[i], titles[d.anchorIdx], cfg, drafts[i].file)
607
641
  operations.push({ op: 'merge-into', file: drafts[i].file, target: `${slug}.md`, additions: add })
608
642
  } else if (d.op === 'merge-existing') {
609
643
  const cTitle = stripAdrName(d.target)
610
- const add = genMerge(titles[i], drafts[i].body, captured[i], cTitle, cfg, drafts[i].file)
644
+ const add = await genMerge(titles[i], drafts[i].body, captured[i], cTitle, cfg, drafts[i].file)
611
645
  operations.push({ op: 'merge-into', file: drafts[i].file, target: d.target, additions: add })
612
646
  } else if (d.op === 'delete') {
613
647
  operations.push({ op: 'delete', file: drafts[i].file, reason: d.reason })
@@ -3,28 +3,45 @@ type: JS Module
3
3
  title: skill-meta.mjs
4
4
  resource: npm/scripts/lib/skill-meta.mjs
5
5
  docgen:
6
- crc: 9ff67388
6
+ crc: fd8085f3
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
9
11
  ---
10
12
 
11
13
  ## Огляд
12
14
 
13
- Спільний парсер метаданих скіла, що зчитує дані з `main.json`. `main.json` є єдиним джерелом правди для скіла, визначаючи його умови автоактивації (`auto`, де `SKILL_ALWAYS` означає "завжди"), чи виконувати скіл в окремому git-worktree (`worktree`), та чи вимагає він запуску з кореня репозиторію (`requireRoot`). Модуль надає функції для читання сирих метаданих та визначення умов, використовуючи механізм fail-safe, який перехоплює помилки, не кидаючи винятків. Цим хелпером користуються `auto-skills.mjs`, `n-cursor.js` та `npm-module/js/skill_meta.mjs`.
15
+ Спільний парсер метаданих скіла з `npm/skills/<id>/main.json`. Цей механізм зчитує та інтерпретує конфігурацію скіла, використовуючи `main.json` як єдине джерело правди. Він визначає умову автоактивації (`auto`, де `SKILL_ALWAYS` означає безумовну активацію), необхідність роботи з кореневою директорією (`requireRoot`), та визначає, чи повинен скіл виконуватися ізольовано в окремому git-worktree (`worktree`).
16
+
17
+ Поведінка
18
+ SKILL_ALWAYS — Константа-рядок, яка позначає, що скіл має бути активований завжди.
19
+ SKILL_TIERS — Список допустимих рівнів моделі для виконання скіла.
20
+ DEFAULT_SKILL_TIER — Константа-рядок, яка встановлює рівень моделі за замовчуванням, якщо він не вказаний у метаданих.
21
+ parseSkillAutoSpec — Визначає умови, за яких скіл може бути автоматично активований, ґрунтуючись на конфігурації `main.json`.
22
+ skillRequiresRoot — Визначає, чи вимагає скіл запуску з кореневої директорії репозиторію. Зверніть увагу, що скіли з `worktree:true` не вимагають цього поля явно, оскільки коренем є сам worktree.
23
+ skillTier — Визначає рівень моделі, який буде використаний для виконання скіла, враховуючи конфігурацію `main.json` або повертаючи `DEFAULT_SKILL_TIER`.
24
+ readSkillMetaRaw — Зчитує та парсить сирі метадані скіла з файлу `main.json`. При виявленні помилок парсингу або в IO-операціях, функція не генерує винятки, а повертає `null` (fail-safe).
14
25
 
15
26
  ## Поведінка
16
27
 
17
- SKILL_ALWAYS — надає літерал для безумовної автоактивації скіла.
18
- parseSkillAutoSpecперетворює значення поля `auto` з `main.json` у специфікацію автоактивації скіла.
19
- skillRequiresRootвизначає, чи вимагає скіл запуску з кореня репозиторію, на основі метаданих.
20
- readSkillMetaRawчитає та парсить файл `main.json` з каталогу скіла, повертаючи його вміст або `null` у разі помилки.
28
+ SKILL_ALWAYS — Константа, що позначає безумовну активацію скіла.
29
+ SKILL_TIERSСписок допустимих рівнів моделі для виконання скіла.
30
+ DEFAULT_SKILL_TIERТир моделі, який застосовується за замовчуванням, якщо рівень не вказаний у метаданих.
31
+ parseSkillAutoSpecВизначає, як скіл може бути автоматично активований на основі конфігурації `main.json`.
32
+ skillRequiresRoot — Визначає, чи повинен скіл виконуватися з кореневої директорії репозиторію, базуючись на налаштуваннях `main.json`.
33
+ skillTier — Визначає рівень моделі, який буде використано для виконання скіла, приймаючи в до уваги значення з `main.json` або повертаючи `DEFAULT_SKILL_TIER`.
34
+ readSkillMetaRaw — Зчитує та парсить метадані скіла з файлу `main.json` за вказаним шляхом, обробляючи можливі помилки формату.
21
35
 
22
36
  ## Публічний API
23
37
 
24
- SKILL_ALWAYS — позначка для безумовної активації
25
- parseSkillAutoSpecвитягує налаштування автоматичного запуску зі `main.json`
26
- skillRequiresRootвизначає, чи потрібен запуск скіла з кореня репозиторію
27
- readSkillMetaRawзчитує та аналізує метадані окремого скіла з `main.json`
38
+ **SKILL_ALWAYS**позначка, що робить скіл автоматично активним у будь-якій ситуації.
39
+ **SKILL_TIERS**список допустимих рівнів (тирів) моделей, які можуть виконувати скіл через `pi`-runner.
40
+ **DEFAULT_SKILL_TIER**рівень моделі, який використовується за замовчуванням для скілів, якщо `main.json` цього скіла не вказує конкретний рівень.
41
+ **parseSkillAutoSpec**визначає, чи має скіл автоматично запускатися на основі конфігурації в `main.json`.
42
+ **skillRequiresRoot** — позначає, чи повинен скіл виконуватися з кореневої директорії репозиторію; це захист від несанкціонованого запуску.
43
+ **skillTier** — визначає необхідний рівень моделі для виконання скіла, використовуючи значення з `main.json` або встановлюючи `DEFAULT_SKILL_TIER`.
44
+ **readSkillMetaRaw** — зчитує та аналізує метадані конкретного скіла з файлу `main.json`.
28
45
 
29
46
  ## Гарантії поведінки
30
47
 
@@ -3,25 +3,26 @@ type: JS Module
3
3
  title: escalation-log.mjs
4
4
  resource: npm/scripts/lib/fix/escalation-log.mjs
5
5
  docgen:
6
- crc: 07ae959f
6
+ crc: 91898427
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Append-only JSONL-лог драбини ескалації конформність-фіксу. Один запис на рунг драбини: модель, чи виклик удався, чи правило стало зеленим після рунга («чи допомогло»), залишковий violation і само-аналіз моделі (`diagnosis`). Доповнює always-on wire-trace — той знає вміст викликів, але не результат re-check; join — за полем `caller` (`fix:<rule>:<rung>`).
11
+ ## Огляд
12
12
 
13
- ## Поведінка
13
+ Файл створює append-only JSONL-лог ескалації конформність-фіксу (спека 2026-06-19-fix-escalation-cascade-design). Він фіксує кожен запис на рунг драбини, що включає деталі використаної моделі (`model`), подання зворотного зв'язку (`withFeedback`), результат виклику (`callOk`/`callError`), оцінку, чи допомогло це усунути порушення (`recheckOk`), залишковий `violation` та самоаналіз моделі (`diagnosis`). Цей лог доповнює always-on wire-trace (`lib/pi-trace.mjs`) і формує логіку для join за полем `caller` (`fix:<rule>:<rung>`). Запис відбувається за певним шільною: або через kill-switch `N_CURSOR_FIX_ESCALATION_LOG`, або за явним шляхом, дефолтно у `<cwd>/.n-cursor/fix-escalation.jsonl`.
14
14
 
15
- `escalationLogPath` резолвить шлях: `N_CURSOR_FIX_ESCALATION_LOG` як kill-switch (`0|false|off|no` → лог вимкнено, повертає `null`) або як явний шлях; інакше дефолт `<cwd>/.n-cursor/fix-escalation.jsonl`.
15
+ ## Поведінка
16
16
 
17
- `logEscalation` дописує один JSONL-рядок (no-op, якщо лог вимкнено). Поля `remainingViolation` і `diagnosis` обрізаються до межі; `recheckOk` обнуляє `remainingViolation`. Помилки запису ковтаються — лог діагностичний і не має валити сам фікс.
17
+ Поведінка:
18
+ escalationLogPath визначає шлях до файлу журналу ескалації конформності. Якщо встановлено змінну середовища N_CURSOR_FIX_ESCALATION_LOG, використовується цей шлях; інакше, за замовчуванням, це .n-cursor/fix-escalation.jsonl у поточній робочій директорії.
19
+ logEscalation записує один запис про рунг у JSONL-лог, якщо шлях до логу визначено. Запис містить метадані про спробу фіксування, результати виклику та аналіз. Помилки під час запису логу ігноруються.
18
20
 
19
21
  ## Публічний API
20
22
 
21
- - `escalationLogPath()`шлях активного логу або `null`, якщо вимкнено.
22
- - `logEscalation(rec)`дописує запис рунга у JSONL.
23
+ escalationLogPath — вказує на місце зберігання логу ескалацій, якщо функція не вимкнена.
24
+ logEscalation — записує один подію виконання в спеціальний лог у форматі JSONL, ігноруючи внутрішні помилки запису.
23
25
 
24
26
  ## Гарантії поведінки
25
27
 
26
- - Не звертається до мережі.
27
- - Перехоплює помилки запису і не пропускає винятків назовні (fail-safe).
28
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).