@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.16.1] - 2026-06-26
4
+
5
+ ### Changed
6
+
7
+ - docs(skill-meta): уточнено опис tier — абстрактна тира (capability), не провайдер; кожна каскадить local→cloud
8
+
9
+ ## [12.16.0] - 2026-06-26
10
+
11
+ ### Added
12
+
13
+ - feat: skills-cli pi-runner + pi-agent-skill — надбудова skills-CLI поверх pi
14
+
15
+ ### Changed
16
+
17
+ - feat: pi-native agentic fix-engine — міграція з omlx на pi (squash)
18
+
3
19
  ## [12.15.1] - 2026-06-26
4
20
 
5
21
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -1544,7 +1544,7 @@ try {
1544
1544
  break
1545
1545
  }
1546
1546
  case 'skill': {
1547
- process.exitCode = runSkillsCli(args)
1547
+ process.exitCode = await runSkillsCli(args)
1548
1548
 
1549
1549
  break
1550
1550
  }
package/lib/docs/index.md CHANGED
@@ -6,9 +6,12 @@ resource: npm/lib/
6
6
 
7
7
  # npm/lib
8
8
 
9
- | Файл | Тип |
10
- | ------------------------------- | --------- |
11
- | [llm.mjs](llm.md) | JS Module |
12
- | [models.mjs](models.md) | JS Module |
13
- | [omlx-trace.mjs](omlx-trace.md) | JS Module |
14
- | [omlx.mjs](omlx.md) | JS Module |
9
+ | Файл | Тип |
10
+ | ----------------------------------------------- | --------- |
11
+ | [pi-agent-fix.mjs](pi-agent-fix.md) | JS Module |
12
+ | [pi-agent-skill.mjs](pi-agent-skill.md) | JS Module |
13
+ | [pi-model-tiers.mjs](pi-model-tiers.md) | JS Module |
14
+ | [pi-one-shot.mjs](pi-one-shot.md) | JS Module |
15
+ | [pi-telemetry-store.mjs](pi-telemetry-store.md) | JS Module |
16
+ | [pi-trace.mjs](pi-trace.md) | JS Module |
17
+ | [pi-write-guard.mjs](pi-write-guard.md) | JS Module |
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-agent-fix.mjs
4
+ resource: npm/lib/pi-agent-fix.mjs
5
+ docgen:
6
+ crc: 4cf083e2
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Файл надає інструментарій для автоматизованого виправлення кодів згідно з визначеними правилами. За допомогою функції `buildFixPrompt` формується деталізований запит для агента, що містить опис порушення та відповідне правило. Далі, `runPiAgentFix` ініціює процес застосування виправлень у проєкті. Цей процес реалізовано з механізмом безпеки (fail-safe), що гарантує відсутність винятків на виході.
14
+
15
+ ## Поведінка
16
+
17
+ Поведінка:
18
+ buildFixPrompt створює текстовий опис завдання для агента, інформуючи про виявлене порушення правила, надаючи текст самого правила та звіт про попередні невдачі.
19
+ runPiAgentFix запускає цикл виправлення з використанням агентної сесії, застосовуючи патч у файли проєкту, управляючи безпекою змін, збираючи телеметрію та повертаючи результат виконання.
20
+
21
+ ## Публічний API
22
+
23
+ buildFixPrompt — Створює деталізований запит для автоматичного виправлення, включаючи оригінальне правило, описане порушення та можливий відгук щодо попередніх невдач. Додає в інструкцію вимогу попереднього аналізу AST та самоперевірки після редагування.
24
+ runPiAgentFix — Ініціює єдину спробу автоматичного виправлення (руна) з використанням AI-агента, передаючи йому деталі, такі як модель, рівень пріоритету, відгук, викликаючий агент та інші конфігураційні параметри.
25
+
26
+ ## Гарантії поведінки
27
+
28
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -0,0 +1,36 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-agent-skill.mjs
4
+ resource: npm/lib/pi-agent-skill.mjs
5
+ docgen:
6
+ crc: 2c9fe32d
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Виконує один скіл `@nitra/cursor` як вбудованого pi-агента через публічну функцію `runPiAgentSkill`: подає готовий промпт у сесію, де агент сам **читає, редагує й створює файли та виконує shell-команди** (`bash`) у робочому каталозі. Це повний user-trust (паритет із ручним `claude -p`) — write-guard не накладається, бо скіл запускає користувач явно. Модель береться з однієї тири (`min`/`avg`/`max`, дефолт `max`). Будь-яка помилка перехоплюється (fail-safe) і повертається у результаті, а не кидається назовні.
16
+
17
+ ## Поведінка
18
+
19
+ 1. Ініціалізація сесії агента: Визначає конфігурацію агента на основі вказаних параметрів, включаючи обраний рівень моделі, робочу директорію та рівень обчислення.
20
+ 2. Виконання скілу: Запускає агентну сесію з поданим промптом.
21
+ 3. Обробка відповідей: Під час виконання скілу, відповіді асистента виводяться у стандартний потік виводу.
22
+ 4. Моніторинг: Відстежується кількість ітерацій та викликів інструментів.
23
+ 5. Захист від нескінченності: Застосовується механізм обмеження кількості ітерацій (`TURN_CEILING`) та загального тайм-ауту для запобігання зависанню.
24
+ 6. Обробка збоїв: У разі виникнення помилки під час ініціалізації, конфігурації або виконання, вона фіксується, а функція повертає стан невдачі.
25
+ 7. Фіналізація: Після завершення виконання (успішно або через таймаут/переповнення ліміту) збирається телеметрія, яка включає статистику сесії, і результат повертається у вигляді об'єкта з інформацією про успішність.
26
+
27
+ ## Публічний API
28
+
29
+ runPiAgentSkill — Викликає певний агентський функціонал (скіл) через платформу pi з визначеними параметрами складності та обмеженнями виконання.
30
+
31
+ ## Гарантії поведінки
32
+
33
+ - Повний user-trust: агент має read/edit/write/bash і мутує проєкт у робочому каталозі (без write-guard).
34
+ - Runaway-backstop: перевищення стелі ітерацій (`N_CURSOR_SKILL_TURN_CEILING`) або таймауту (`N_CURSOR_SKILL_TIMEOUT_MS`) обриває сесію.
35
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe); повертає `{ ok, telemetry, error }`.
36
+ - Pi вантажиться lazy (тверда межа CI — модуль pi-free до першого виклику).
@@ -0,0 +1,46 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-model-tiers.mjs
4
+ resource: npm/lib/pi-model-tiers.mjs
5
+ docgen:
6
+ crc: 05b8d5cd
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей модуль відповідає за конфігурацію та забезпечення доступу до моделей, спираючись на дані з `models.json` та `auth.json`. Він надає параметри для локальних (`LOCAL_MIN` по `LOCAL_MAX`) та хмарних (`CLOUD_MIN` по `CLOUD_MAX`) моделей, а також реалізує логіку для визначення відповідного рівня мислення за заданим тиром. Основна функція полягає в ініціалізації глобального реєстру моделей (`getRegistry`) та розпізнаванні специфікаторів за допомогою `parseModelId`.
14
+
15
+ ## Поведінка
16
+
17
+ Поведінка:
18
+ LOCAL_MIN — надає конфігураційне значення для мінімальної локальної моделі.
19
+ LOCAL_AVG — надає конфігураційне значення для середньої локальної моделі.
20
+ LOCAL_MAX — надає конфігураційне значення для максимальної локальної моделі.
21
+ CLOUD_MIN — надає конфігураційне значення для мінімальної хмарної моделі.
22
+ CLOUD_AVG — надає конфігураційне значення для середньої хмарної моделі.
23
+ CLOUD_MAX — надає конфігураційне значення для максимальної хмарної моделі.
24
+ resolveModel — визначає шлях до моделі на основі вибраного рівня (min, avg, max) за каскадною політикою.
25
+ thinkingLevelForTier — визначає відповідний дискретний рівень мислення залежно від rung-тиру моделі.
26
+ parseModelId — розбиває специфікатор моделі у форматі `"provider/model-id"` на окремі частини.
27
+ resolveModelSpec — знаходить об'єкт моделі у регістра через наданий специфікатор, використовуючи інжектований registry.
28
+ getRegistry — асинхронно ініціалізує та повертає екземпляр `ModelRegistry` з конфігурацій `models.json` та `auth.json`.
29
+
30
+ ## Публічний API
31
+
32
+ LOCAL_MIN — Забезпечує швидке виконання моделей без використання хмарних сервісів.
33
+ LOCAL_AVG — Надає середні можливості локальних моделей для виконання завдань.
34
+ LOCAL_MAX — Дозволяє використовувати найпотужніші доступні локальні моделі.
35
+ CLOUD_MIN — Забезпечує мінімальний рівень продуктивності від хмарних моделей, вимагає підключення через `pi auth`.
36
+ CLOUD_AVG — Використовує середній за можливостями хмарний двигун.
37
+ CLOUD_MAX — Дозволяє отримати максимальну якість від хмарних моделей.
38
+ resolveModel — Визначає найкращу модель для виконання, обираючи між локальними та хмарними варіантами залежно від запитуваного рівня (`min`, `avg`, `max`).
39
+ thinkingLevelForTier — Призначає відповідний рівень обробки (`low`, `medium`, `high`) на основі обраного рівня моделі для коректної роботи з пам'яттю та ресурсами.
40
+ parseModelId — Розбирає загальний рядок ідентифікатора моделі у дві частини: назву постачальника та його ідентифікатор.
41
+ resolveModelSpec — Перетворює текстовий рядок ідентифікатора моделі у структурований об'єкт, який використовується всіма компонентами системи.
42
+ getRegistry — Зберігає ідентифікаторів та специфікацій усіх доступних моделей у єдиній кешованій довіднику.
43
+
44
+ ## Гарантії поведінки
45
+
46
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-one-shot.mjs
4
+ resource: npm/lib/pi-one-shot.mjs
5
+ docgen:
6
+ crc: d69067a3
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей механізм виконує одноразовий запит до мовної моделі (LLM) через публічну функцію `runOneShot`. Він збирає необхідну інформацію, ініціює взаємодію та консолідує вхідні дані у єдиний текстовий запит. Функція завжди перехоплює помилки (fail-safe) та не кидає винятків назовні. У разі успішного отримання відповіді від LLM, що є операцією лише для читання (read-only), повертається згенерований текст, дані про використання моделі та метадані.
14
+
15
+ ## Поведінка
16
+
17
+ Поведінка:
18
+
19
+ 1. Для виконання одноразового LLM-виклику спочатку збирається необхідна інформація для доступу до моделі, використовуючи надані рівні моделі.
20
+ 2. Після визначення моделі створюється сесія для взаємодії з LLM, використовуючи налаштування, які гарантують виконання завдання без використання інструментів.
21
+ 3. Всі вхідні повідомлення об'єднуються в єдиний текстовий запит.
22
+ 4. Система чекає відповіді від LLM, обмеженої заданим часовим лімітом.
23
+ 5. Під час очікування відбувається збір генерованого тексту та метаданих об'єднання (usage) відповіді.
24
+ 6. У разі будь-якої помилки під час пошуку моделі, створення сесії чи самого виклику, виклик завершується з повідомленням про помилку.
25
+ 7. У разі успіху, функція повертає згенерований текст, інформацію про використання моделі, статус помилки та метадані моделі, використовуючи інформацію, зібрану під час взаємодії з сесією.
26
+
27
+ ## Публічний API
28
+
29
+ - runOneShot — виконує обмежений LLM-виклик одноразово.
30
+
31
+ ## Гарантії поведінки
32
+
33
+ - Read-only: не виконує операцій запису (ФС/БД).
34
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -0,0 +1,33 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-telemetry-store.mjs
4
+ resource: npm/lib/pi-telemetry-store.mjs
5
+ docgen:
6
+ crc: e6ec7d6c
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей модуль керує життєвим циклом та записом даних телеметрії. Він надає функції для визначення директорії телеметрії, генерації унікального ідентифікатора запису, реєстрації виправлень телеметрії та зчитування кількості відкритих записів. Система функціонує з механізмом безпечного перехоплення помилок, не генеруючи винятків. При виникненні певних збоїв модуль може повертати пусте значення замість викидання помилки.
14
+
15
+ ## Поведінка
16
+
17
+ Поведінка:
18
+ telemetryDir визначає базовий шлях для зберігання глобальних даних телеметрії.
19
+ signatureOf створює унікальний короткий хеш для запису, що представляє структурну сутність правок.
20
+ recordFixTelemetry додає або оновлює запис про спробу виправлення у глобальному сторі, обробляючи потенційні секрети та повтори.
21
+ openCount підраховує кількість відкритих записів для заданого правила у телеметричному сторі.
22
+
23
+ ## Публічний API
24
+
25
+ telemetryDir — вказує на директорію, де зберігаються дані телеметрії, яку можна перевизначити через змінну середовища `N_CURSOR_TELEMETRY_DIR`.
26
+ signatureOf — генерує унікальний ідентифікатор для збереження змін, поєднуючи назву правила та структуру внесених правок.
27
+ recordFixTelemetry — записує або згортає одну спробу виправлення у сховище даних телеметрії, ніколи не викликаючи помилку.
28
+ openCount — фіксує кількість активних записів, пов'язаних із певним правилом, що допомагає визначити поріг для дистиляційного аналізу.
29
+
30
+ ## Гарантії поведінки
31
+
32
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
33
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-trace.mjs
4
+ resource: npm/lib/pi-trace.mjs
5
+ docgen:
6
+ crc: da5862a0
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей модуль реалізує механізм фіксації шляхів трасування для процесів LLM. Він надає функції `tracePath` для ініціалізації трасування шляху та `writeTrace` для додавання нових записів до глобального трасування. Механізм розроблено з принципом fail-safe, що гарантує стабільність роботи, перехоплюючи будь-які помилки файлової системи без генерації винятків.
14
+
15
+ ## Поведінка
16
+
17
+ tracePath визначає повний шлях до глобального файлу трасування LLM, використовуючи змінну оточення `N_CURSOR_TRACE_PATH` або шлях за замовчуванням у домашньому каталозі.
18
+ writeTrace додає новий запис трасування у файл, забезпечуючи створення необхідних директорій, але при цьому ігнорує будь-які помилки введення/виведення для забезпечення стійкості виклику LLM.
19
+
20
+ ## Публічний API
21
+
22
+ tracePath — Вказує на розташування глобального трасувального файлу.
23
+ writeTrace — Записує окремий запис трасування у форматі JSONL.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: JS Module
3
+ title: pi-write-guard.mjs
4
+ resource: npm/lib/pi-write-guard.mjs
5
+ docgen:
6
+ crc: d18851b6
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль забезпечує безпечну взаємодію з файловою системою в межах структури Git-репозиторію. Він визначає кореневу директорію Git-репозиторію, створює механізм захисту від запису та відстежує нові файли для підтримки механізму відкату змін.
14
+
15
+ Функція NEW_FILE фіксує шляхи до нових файлів у системі, що допомагає коректно керувати їхнім станом під час відкату змін. Функція gitRoot встановлює директорію кореня Git-репозиторію, виходячи з поточної робочої директорії. Механізм createWriteGuard запобігає несанкціонованим змінам файлів, перехоплюючи спроби модифікації та ігноруючи директорії, такі як .git, гарантуючи при цьому безпечний вихід із будь-яких помилок.
16
+
17
+ ## Поведінка
18
+
19
+ NEW_FILE — Фіксує, що файл, якого до запису ще не існувало, позначається для подальшого видалення при відкаті.
20
+ gitRoot — Визначає кореневу директорію Git-репозиторію, починаючи з поточної робочої директорії, або повертає null, якщо репозиторій не виявлено.
21
+ createWriteGuard — Створює захист від запису, який перехоплює спроби агентів модифікувати файли, перевіряючи їхню область дії відносно git-репозиторію, запобігаючи запису у `.git/` та ігнорованих файлів, а також зберігаючи попередній стан файлів перед їх зміною.
22
+
23
+ ## Публічний API
24
+
25
+ NEW_FILE — Позначає ціль, яку слід відновити, якщо файл не існував до спроби запису.
26
+ gitRoot — Визначає кореневу директорію Git-репозиторію поточного робочого каталогу; дорівнює null, якщо це не Git-репозиторій.
27
+ createWriteGuard — Створює захист від запису для однієї сесії виправлення, що включає визначення поточного робочого каталогу, кореня Git, та механізм для тестування.
28
+
29
+ ## Гарантії поведінки
30
+
31
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
32
+ - Свідомо пропускає шляхи: `.git`.
@@ -0,0 +1,253 @@
1
+ /** @see ./docs/pi-agent-fix.md */
2
+
3
+ /**
4
+ * Agentic fix-worker поверх pi `createAgentSession` (§1/§2/§5/§12 спеки pi-migration).
5
+ *
6
+ * Замінює `llm-worker.runLlmWorker` (`{changes}`+`applyChanges`) на агентний цикл, де
7
+ * pi сам читає контекст і **застосовує патч** вбудованими `edit`/`write`. Інтегрує:
8
+ * - тири → Model ([pi-model-tiers]),
9
+ * - write-safety §12 ([pi-write-guard]) через `DefaultResourceLoader` + **fail-closed canary**,
10
+ * - custom-tools `ast_facts` (§3б) і `self_check` (§4+5, advisory),
11
+ * - turn-ceiling backstop (`session.abort()` на перевищенні),
12
+ * - телеметрію §7 (turns/tool-calls/повні edits) у [pi-trace].
13
+ *
14
+ * Контракт (application-agnostic seam §2): повертає `{ applied, touchedFiles, telemetry,
15
+ * error, rollback }` — застосуванням володіє worker, orchestrator робить лише зовнішній
16
+ * verdict-recheck і за провалу кличе `rollback()` (clean-slate per rung).
17
+ *
18
+ * Pi вантажиться lazy (тверда межа CI). Логіка інжектована через `deps` для unit-тестів.
19
+ */
20
+
21
+ import { env } from 'node:process'
22
+ import { homedir } from 'node:os'
23
+ import { resolve } from 'node:path'
24
+ import { getRegistry, resolveModelSpec, thinkingLevelForTier } from './pi-model-tiers.mjs'
25
+ import { createWriteGuard, gitRoot } from './pi-write-guard.mjs'
26
+ import { writeTrace } from './pi-trace.mjs'
27
+ import { extractContext } from '../scripts/utils/ast-extract.mjs'
28
+
29
+ /** Аварійна стеля turns на одну сесію (runaway-backstop §4+5). Override: `N_CURSOR_FIX_TURN_CEILING`. */
30
+ const TURN_CEILING = Number(env.N_CURSOR_FIX_TURN_CEILING) || 50
31
+
32
+ /**
33
+ * Будує fix-промпт для рунга: правило + порушення + (опц.) feedback попереднього провалу
34
+ * + інструкція «ast_facts перед edit, self_check після».
35
+ * @param {{ ruleId: string, violation: string, ruleText?: string, feedback?: object }} args
36
+ * @returns {string} промпт
37
+ */
38
+ export function buildFixPrompt({ ruleId, violation, ruleText, feedback }) {
39
+ const parts = [`Виправ порушення правила "${ruleId}" у цьому проєкті.`]
40
+ if (ruleText) parts.push(`## Правило\n${ruleText}`)
41
+ parts.push(`## Порушення\n${violation}`)
42
+ if (feedback?.previousError) {
43
+ parts.push(`## Попередня спроба не спрацювала\n${feedback.previousError}\nСпробуй інший підхід.`)
44
+ }
45
+ parts.push(
46
+ 'Перед редагуванням JS/TS-файлу спершу виклич `ast_facts` на ньому. ' +
47
+ 'Після правок виклич `self_check`, щоб підтвердити, що порушення зникло. ' +
48
+ 'Редагуй лише потрібне, не чіпай стороннє.'
49
+ )
50
+ return parts.join('\n\n')
51
+ }
52
+
53
+ /** Гонка з таймаутом; на таймаут кличе `onTimeout` (abort) і реджектить. */
54
+ function withTimeout(promise, ms, onTimeout) {
55
+ if (!ms || ms <= 0) return promise
56
+ let timer
57
+ const timeout = new Promise((_, reject) => {
58
+ timer = setTimeout(() => {
59
+ onTimeout?.()
60
+ reject(new Error(`fix timeout ${ms}ms`))
61
+ }, ms)
62
+ })
63
+ return Promise.race([Promise.resolve(promise).finally(() => clearTimeout(timer)), timeout])
64
+ }
65
+
66
+ /**
67
+ * Дефолтна фабрика pi-сесії: loader із write-guard, custom-tools ast_facts+self_check.
68
+ * @returns {Promise<object>} pi AgentSession
69
+ */
70
+ async function defaultCreateSession({ registry, model, thinkingLevel, cwd, factory, astContext, selfCheck }) {
71
+ const { createAgentSession, SessionManager, DefaultResourceLoader, SettingsManager, defineTool } =
72
+ await import('@earendil-works/pi-coding-agent')
73
+ const agentDir = `${homedir()}/.pi/agent`
74
+ const loader = new DefaultResourceLoader({
75
+ cwd,
76
+ agentDir,
77
+ settingsManager: SettingsManager.create(cwd, agentDir),
78
+ extensionFactories: [factory]
79
+ })
80
+ await loader.reload()
81
+
82
+ const astTool = defineTool({
83
+ name: 'ast_facts',
84
+ label: 'AST facts',
85
+ description:
86
+ 'Extract structured AST facts (imports, exports, top-level functions) from a JS/TS source file. Call this before editing a file to understand it without reading the whole content.',
87
+ parameters: {
88
+ type: 'object',
89
+ properties: { path: { type: 'string', description: 'file path' } },
90
+ required: ['path']
91
+ },
92
+ execute: async (_id, { path }) => ({
93
+ content: [{ type: 'text', text: JSON.stringify(astContext(path)) }],
94
+ details: {}
95
+ })
96
+ })
97
+ const checkTool = defineTool({
98
+ name: 'self_check',
99
+ label: 'Self check',
100
+ description:
101
+ 'Re-run the rule check on the given files to see whether the violation is resolved. Advisory: use it to decide if more edits are needed.',
102
+ parameters: { type: 'object', properties: { files: { type: 'array', items: { type: 'string' } } }, required: [] },
103
+ execute: async (_id, { files }) => ({
104
+ content: [{ type: 'text', text: JSON.stringify(await selfCheck(files ?? [])) }],
105
+ details: {}
106
+ })
107
+ })
108
+
109
+ const { session } = await createAgentSession({
110
+ modelRegistry: registry,
111
+ model,
112
+ thinkingLevel,
113
+ cwd,
114
+ tools: ['read', 'grep', 'find', 'edit', 'write', 'ls', 'ast_facts', 'self_check'],
115
+ customTools: [astTool, checkTool],
116
+ sessionManager: SessionManager.inMemory(),
117
+ resourceLoader: loader
118
+ })
119
+ return session
120
+ }
121
+
122
+ /**
123
+ * Проводить ОДНУ агентну fix-спробу (рунг) для правила.
124
+ * @param {string} ruleId id правила
125
+ * @param {string} violation violation-output (вже з concern-маркерами §1а)
126
+ * @param {string} cwd корінь проєкту
127
+ * @param {{
128
+ * model: string, tier?: string, feedback?: object, caller?: string, timeoutMs?: number, ruleText?: string,
129
+ * deps?: { createSession?: Function, getRegistry?: Function, registry?: object, root?: string|null,
130
+ * astContext?: Function, selfCheck?: Function, trace?: Function, clock?: () => number }
131
+ * }} opts
132
+ * @returns {Promise<{ applied: boolean, touchedFiles: string[], telemetry: object|null, error: string|null, rollback: () => void }>}
133
+ */
134
+ export async function runPiAgentFix(ruleId, violation, cwd, opts = {}) {
135
+ const { model: modelSpec, tier = null, feedback, caller = `fix:${ruleId}`, timeoutMs, ruleText, deps = {} } = opts
136
+ const createSession = deps.createSession ?? defaultCreateSession
137
+ const getReg = deps.getRegistry ?? getRegistry
138
+ const trace = deps.trace ?? writeTrace
139
+ const clock = deps.clock ?? (() => Date.now())
140
+ const astContext = deps.astContext ?? (p => extractContext(resolve(cwd, p)))
141
+ const selfCheck = deps.selfCheck ?? (async () => ({ ok: false, output: 'self_check недоступний' }))
142
+ const noop = () => {}
143
+
144
+ const fail = error => {
145
+ trace({ caller, backend: 'pi-ai', kind: 'agent', rule: ruleId, rung: tier, model: modelSpec, cwd, error })
146
+ return { applied: false, touchedFiles: [], telemetry: null, error, rollback: noop }
147
+ }
148
+
149
+ // §12 git-precondition: нема git → fix пропускається.
150
+ const root = deps.root !== undefined ? deps.root : gitRoot(cwd)
151
+ if (!root) return fail('fix пропущено: не git-репо (§12 precondition)')
152
+
153
+ let registry
154
+ let model
155
+ try {
156
+ registry = deps.registry ?? (await getReg())
157
+ model = resolveModelSpec(registry, modelSpec)
158
+ if (!model) return fail(`модель не знайдена: ${modelSpec}`)
159
+ } catch (e) {
160
+ return fail(`registry: ${e.message}`)
161
+ }
162
+
163
+ const guard = createWriteGuard({ cwd, root })
164
+ let session
165
+ try {
166
+ session = await createSession({
167
+ registry,
168
+ model,
169
+ thinkingLevel: thinkingLevelForTier(tier ?? ''),
170
+ cwd,
171
+ factory: guard.factory,
172
+ astContext,
173
+ selfCheck
174
+ })
175
+ } catch (e) {
176
+ return fail(`session: ${e.message}`)
177
+ }
178
+
179
+ // §12 fail-closed canary: guard мусив приєднатись через loader.
180
+ if (!guard.state.attached) return fail('write-guard не приєднався — fix скасовано (§12 fail-closed)')
181
+
182
+ // Телеметрія §7 із subscribe + turn-ceiling backstop.
183
+ const turns = []
184
+ let turnCount = 0
185
+ let toolCallCount = 0
186
+ let backstopHit = false
187
+ session.subscribe(event => {
188
+ switch (event.type) {
189
+ case 'turn_start':
190
+ turnCount++
191
+ turns.push({ i: turnCount, toolCalls: [], usage: null, finish: null })
192
+ if (turnCount > TURN_CEILING) {
193
+ backstopHit = true
194
+ session.abort?.()
195
+ }
196
+ break
197
+ case 'tool_execution_start':
198
+ toolCallCount++
199
+ break
200
+ case 'tool_execution_end':
201
+ turns.at(-1)?.toolCalls.push({ name: event.toolName, status: event.isError ? 'error' : 'ok' })
202
+ break
203
+ case 'message_end': {
204
+ const t = turns.at(-1)
205
+ if (t && event.message?.usage) {
206
+ t.usage = event.message.usage
207
+ t.finish = event.message.stopReason ?? null
208
+ }
209
+ break
210
+ }
211
+ default:
212
+ break
213
+ }
214
+ })
215
+
216
+ const startedAt = clock()
217
+ let error = null
218
+ try {
219
+ await withTimeout(session.prompt(buildFixPrompt({ ruleId, violation, ruleText, feedback })), timeoutMs, () =>
220
+ session.abort?.()
221
+ )
222
+ } catch (e) {
223
+ error = e.message
224
+ }
225
+
226
+ const touchedFiles = guard.touchedFiles()
227
+ const telemetry = {
228
+ rule: ruleId,
229
+ rung: tier,
230
+ model: modelSpec,
231
+ turns,
232
+ turnCount,
233
+ toolCallCount,
234
+ edits: guard.state.editLog,
235
+ blocks: guard.state.blocks,
236
+ backstopHit,
237
+ wallMs: clock() - startedAt
238
+ }
239
+ trace({
240
+ caller,
241
+ backend: 'pi-ai',
242
+ kind: 'agent',
243
+ rule: ruleId,
244
+ rung: tier,
245
+ model: modelSpec,
246
+ cwd,
247
+ turnCount,
248
+ toolCallCount,
249
+ backstopHit,
250
+ error
251
+ })
252
+ return { applied: touchedFiles.length > 0, touchedFiles, telemetry, error, rollback: guard.rollback }
253
+ }