@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.
- package/CHANGELOG.md +16 -0
- package/bin/n-cursor.js +1 -1
- package/lib/docs/index.md +9 -6
- package/lib/docs/pi-agent-fix.md +28 -0
- package/lib/docs/pi-agent-skill.md +36 -0
- package/lib/docs/pi-model-tiers.md +46 -0
- package/lib/docs/pi-one-shot.md +34 -0
- package/lib/docs/pi-telemetry-store.md +33 -0
- package/lib/docs/pi-trace.md +27 -0
- package/lib/docs/pi-write-guard.md +32 -0
- package/lib/pi-agent-fix.mjs +253 -0
- package/lib/pi-agent-skill.mjs +181 -0
- package/lib/pi-model-tiers.mjs +109 -0
- package/lib/pi-one-shot.mjs +129 -0
- package/lib/pi-telemetry-store.mjs +0 -0
- package/lib/pi-trace.mjs +40 -0
- package/lib/pi-write-guard.mjs +147 -0
- package/package.json +5 -1
- package/rules/doc-files/js/docgen-files-batch.mjs +20 -5
- package/rules/doc-files/js/docgen-gen.mjs +42 -25
- package/rules/doc-files/js/docgen-judge-measure.mjs +16 -13
- package/rules/doc-files/js/docgen-judge.mjs +11 -9
- package/rules/doc-files/js/docs/docgen-files-batch.md +3 -20
- package/rules/doc-files/js/docs/docgen-gen.md +3 -20
- package/rules/doc-files/js/docs/docgen-judge-measure.md +3 -18
- package/rules/doc-files/js/docs/docgen-judge.md +3 -22
- package/rules/npm-module/js/docs/skill_meta.md +22 -15
- package/rules/npm-module/js/skill_meta.mjs +5 -1
- package/rules/text/js/cspell-fix.mjs +15 -16
- package/rules/text/js/docs/cspell-fix.md +16 -9
- package/rules/text/main.mjs +4 -4
- package/schemas/skill-meta.json +8 -0
- package/scripts/docs/skills-cli.md +21 -25
- package/scripts/lib/adr/docs/normalize-cli.md +3 -20
- package/scripts/lib/adr/docs/normalize-pipeline.md +3 -33
- package/scripts/lib/adr/normalize-cli.mjs +2 -2
- package/scripts/lib/adr/normalize-pipeline.mjs +78 -44
- package/scripts/lib/docs/skill-meta.md +27 -10
- package/scripts/lib/fix/docs/escalation-log.md +10 -9
- package/scripts/lib/fix/docs/orchestrator.md +13 -20
- package/scripts/lib/fix/escalation-log.mjs +1 -1
- package/scripts/lib/fix/orchestrator.mjs +65 -31
- package/scripts/lib/skill-meta.mjs +22 -0
- package/scripts/skills-cli.mjs +52 -14
- package/scripts/utils/ast-extract.mjs +105 -0
- package/scripts/utils/docs/ast-extract.md +30 -0
- package/lib/docs/llm.md +0 -33
- package/lib/docs/models.md +0 -48
- package/lib/docs/omlx-trace.md +0 -49
- package/lib/docs/omlx.md +0 -41
- package/lib/llm.mjs +0 -215
- package/lib/models.mjs +0 -75
- package/lib/omlx-trace.mjs +0 -158
- package/lib/omlx.mjs +0 -220
- package/scripts/lib/fix/docs/llm-fix-apply.md +0 -31
- package/scripts/lib/fix/docs/llm-lint-fix.md +0 -31
- package/scripts/lib/fix/docs/llm-worker.md +0 -28
- package/scripts/lib/fix/docs/verbose-block.md +0 -27
- package/scripts/lib/fix/llm-fix-apply.mjs +0 -113
- package/scripts/lib/fix/llm-lint-fix.mjs +0 -82
- package/scripts/lib/fix/llm-worker.mjs +0 -346
- package/scripts/lib/fix/verbose-block.mjs +0 -82
package/rules/text/main.mjs
CHANGED
|
@@ -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 ? '
|
|
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)`)
|
package/schemas/skill-meta.json
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
Створює промпт, збираючи інструкцію скілу та контекст поточного проєкту з файлів package.json, tsconfig.json та .n-cursor.json
|
|
22
|
-
|
|
23
|
-
resolveBundledPackageRoot
|
|
24
|
-
Визначає абсолютний шлях до кореня пакета з урахуванням модуля
|
|
17
|
+
## Поведінка
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
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:
|
|
6
|
+
crc: 222bfca4
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score:
|
|
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:
|
|
6
|
+
crc: 8e9ca76f
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score:
|
|
9
|
-
issues:
|
|
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 {
|
|
28
|
-
import { CLOUD_MIN, resolveModel } from '../../../lib/
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
295
|
+
const parse = raw => KindSchema.parse(extractJson(raw))
|
|
281
296
|
try {
|
|
282
|
-
return callWithCascade([{ role: 'system', content: KIND_SYS }, { role: 'user', content: user }], parse, {
|
|
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 =
|
|
451
|
+
const parse = raw => {
|
|
432
452
|
const sections = normalizeSections(extractJson(raw))
|
|
433
|
-
if (!sections.context && !sections.chosen && !sections.rationale)
|
|
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, {
|
|
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, {
|
|
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:
|
|
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
|
-
Спільний парсер метаданих
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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:
|
|
6
|
+
crc: 91898427
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
15
|
+
## Поведінка
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Поведінка:
|
|
18
|
+
escalationLogPath визначає шлях до файлу журналу ескалації конформності. Якщо встановлено змінну середовища N_CURSOR_FIX_ESCALATION_LOG, використовується цей шлях; інакше, за замовчуванням, це .n-cursor/fix-escalation.jsonl у поточній робочій директорії.
|
|
19
|
+
logEscalation записує один запис про рунг у JSONL-лог, якщо шлях до логу визначено. Запис містить метадані про спробу фіксування, результати виклику та аналіз. Помилки під час запису логу ігноруються.
|
|
18
20
|
|
|
19
21
|
## Публічний API
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
escalationLogPath — вказує на місце зберігання логу ескалацій, якщо функція не вимкнена.
|
|
24
|
+
logEscalation — записує один подію виконання в спеціальний лог у форматі JSONL, ігноруючи внутрішні помилки запису.
|
|
23
25
|
|
|
24
26
|
## Гарантії поведінки
|
|
25
27
|
|
|
26
|
-
-
|
|
27
|
-
- Перехоплює помилки запису і не пропускає винятків назовні (fail-safe).
|
|
28
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|