@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
@@ -3,38 +3,31 @@ type: JS Module
3
3
  title: orchestrator.mjs
4
4
  resource: npm/scripts/lib/fix/orchestrator.mjs
5
5
  docgen:
6
- crc: c6f15470
6
+ crc: 49146418
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль відповідає за оркестрацію вирішення порушень. Він парсить аргументи для визначення бюджету та фільтрів за допомогою `parseOrchestratorArgs`. Потім він будує послідовність тирів ескалації, використовуючи `buildLadder`, і може ескалувати правила за допомогою `escalateRule`. Нарешті, він виконує повний цикл фіксації порушень через `runOrchestratorCli`.
13
+ Цей модуль керує процесом виправлення порушень. Він виконує функцію `parseOrchestratorArgs` для визначення обсягу роботи з хмарними моделями та фільтрами правил. Модуль забезпечує класифікацію помилок через `classifyFixError` та ескалацію правил за допомогою `escalateRule`. Крім того, він створює структуру для застосування правил за допомогою `buildLadder` і запускає основний клієнтський процес за допомогою `runOrchestratorCli`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- buildLadder будує послідовність тирів ескалації для вирішення порушень, від локальних до хмарних моделей.
18
- escalateRule проводить один прогін по послідовності тирів, намагаючись вирішити порушення, і повертає статус вирішення та використаний бюджет хмарних викликів.
19
- parseOrchestratorArgs парсить аргументи командного рядка, щоб визначити максимальний бюджет хмарних викликів та фільтр правил.
20
- runOrchestratorCli виконує повний цикл фіксації порушень: початкова перевірка, детермінований фікс T0-auto, ітеративний LLM-фікс за драбиною ескалації, а потім фінальна перевірка.
17
+ Поведінка:
18
+ classifyFixError визначає тип помилки, виниклої при спробі виправлення.
19
+ buildLadder конструює послідовність моделей для багаторівневої спроби усунення порушень.
20
+ escalateRule ітерує по моделях драбини, намагаючись виправити одне порушення, і звітує про результат.
21
+ parseOrchestratorArgs виділяє максимальну кількість викликів хмарної моделі та фільтр правил із вхідних аргументів.
22
+ runOrchestratorCli виконує повний цикл фіксації порушень, включаючи початкову перевірку, детерміноване виправлення та ітерації з LLM.
21
23
 
22
24
  ## Публічний API
23
25
 
24
- buildLadderСтворює послідовність моделей для ескалації, виходячи з доступних рівнів.
25
-
26
- local-minПерший прохід з локальною мінімальною моделлю.
27
- local-min-retryПовторний прохід локальною моделлю з урахуванням результатів попереднього кроку.
28
- cloud-minВикористання мінімальної хмарної моделі з зворотним зв'язком.
29
- cloud-avg — Використання середньої хмарної моделі з зворотним зв'язком, обмежене середнім бюджетом.
30
-
31
- escalateRule — Застосовує одне правило по послідовності ескалації до першого успішного перевірки.
32
- Кожен рунг — Викликає обробника з попереднім результатом, перевіряє правило, фіксує результат у лозі.
33
- Достроковий вихід — Зупиняє процес при певних умовах (відсутність ключа, пропуск моделі на системному рівні) або перевищенні середнього бюджету.
34
-
35
- parseOrchestratorArgs — Витягує максимальне значення середнього бюджету з аргументів командного рядка та збирає фільтри правил.
36
-
37
- runOrchestratorCli — Запускає основний процес оркестрації з командного рядка.
26
+ classifyFixErrorВизначає тип помилки, яку виявив pi-agent-fix: системна, транспортна чи якісна.
27
+ buildLadder — Створює послідовність моделей для усунення помилок, починаючи з найменш ресурсомістких локальних спроб і рухаючись до хмарних моделей, враховуючи попередній досвід.
28
+ escalateRuleЗастосовує одне правило за визначеною драбиною, повторюючи перевірку після кожного кроку та фіксуючи результати у лозі.
29
+ parseOrchestratorArgsЗчитує аргументи командного рядка, зокрема максимальний бюджет середнього рівня, та витягує фільтр правил.
30
+ runOrchestratorCliВиконує основну логіку оркестрації, керуючи циклом виявлення, класифікації та ескалації виправлень.
38
31
 
39
32
  ## Гарантії поведінки
40
33
 
@@ -5,7 +5,7 @@
5
5
  * правило стало зеленим після цього рунга (`recheckOk` = «чи допомогло»),
6
6
  * залишковий violation і `diagnosis` (само-аналіз моделі «чому не вдалося»).
7
7
  *
8
- * Це доповнення до always-on wire-trace (`lib/omlx-trace.mjs`): trace знає
8
+ * Це доповнення до always-on wire-trace (`lib/pi-trace.mjs`): trace знає
9
9
  * `messages`/`reasoning`/`usage` кожного виклику, але **не** знає результату
10
10
  * re-check — а саме «чи допомогло» й потрібне для пост-аналізу драбини. Join із
11
11
  * trace — за полем `caller` (`fix:<rule>:<rung>`), яке цей модуль і формує.
@@ -4,10 +4,9 @@ import { env } from 'node:process'
4
4
  import { runConformanceCheck } from './run-conformance-check.mjs'
5
5
  import { runT0AutoCli } from './t0.mjs'
6
6
  import { logEscalation } from './escalation-log.mjs'
7
- import { runLlmWorker } from './llm-worker.mjs'
8
- import { printVerboseBlock } from './verbose-block.mjs'
9
- import { classifyOmlxError } from '../../../lib/llm.mjs'
10
- import { CLOUD_AVG, CLOUD_MIN, LOCAL_MIN } from '../../../lib/models.mjs'
7
+ import { runPiAgentFix } from '../../../lib/pi-agent-fix.mjs'
8
+ import { recordFixTelemetry } from '../../../lib/pi-telemetry-store.mjs'
9
+ import { CLOUD_AVG, CLOUD_MIN, LOCAL_MIN } from '../../../lib/pi-model-tiers.mjs'
11
10
 
12
11
  /**
13
12
  * Дефолтний кеп на виклики хмарної avg-моделі за один прогін (щоб драбина на N
@@ -16,23 +15,41 @@ import { CLOUD_AVG, CLOUD_MIN, LOCAL_MIN } from '../../../lib/models.mjs'
16
15
  const DEFAULT_MAX_AVG = 3
17
16
 
18
17
  /**
19
- * Timeout одного LLM-виклику (або всієї агентної сесії) за тиром.
20
- * Локальні рунги 5 хвилин: 4b модель повільна, основний backstop — turn-ceiling (~50).
21
- * Хмарні 2 хвилини: API-виклик швидкий, перевищення = transport-помилка.
18
+ * Timeout усієї агентної сесії за тиром. Агентний фікс — багатоходовий
19
+ * (read→edit→self_check, кожен turn = окремий API-раунд), тож на правилах із багатьма
20
+ * порушеннями навіть швидкий cloud не вкладається у пару хвилин (вимір: gpt-5.4-mini
21
+ * timeout на test/doc-files/npm-module за 120s). Тому cloud — теж 5 хв, як local;
22
+ * основний backstop усе одно turn-ceiling (~50) у pi-agent-fix.
22
23
  * Перевизначення: `N_LOCAL_FIX_TIMEOUT_MS` / `N_CLOUD_FIX_TIMEOUT_MS`.
23
24
  */
24
25
  const LOCAL_TIMEOUT_MS = Number(env.N_LOCAL_FIX_TIMEOUT_MS) || 300_000
25
- const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) || 120_000
26
+ const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) || 300_000
26
27
 
27
- /** Маркер дружнього повідомлення про відсутній API-ключ `llm-worker.callModel`). */
28
- const NO_KEY_RE = /немає ключа|api key/i
28
+ /** Реальний транспорт-збій провайдера (мережа/сокет) НЕ наш агентний backstop-timeout. */
29
+ const TRANSPORT_RE = /etimedout|timed out|econnrefused|connection refused/i
29
30
 
30
31
  /**
31
- * Хмарний транспорт (pi) упав на рівні процесу: таймаут/spawn-помилка. Стіна часу
32
- * однакова для всіх cloud-рунгів (та сама pi-транспортна стіна), а cloud-avg — інша
33
- * модель, не більший timeout. Ескалація на неї лише спалить avg-бюджет → обрив.
32
+ * Systemic повтор тієї ж моделі марний: нема git, fail-closed guard, відсутня модель,
33
+ * registry/session/auth. Quality модель видала поганий фікс (retry/escalate може допомогти).
34
34
  */
35
- const CLOUD_TRANSPORT_RE = /etimedout|timed out|pi error/i
35
+ const SYSTEMIC_RE = /не git-репо|fail-closed|write-guard|модель не знайдена|registry:|session:|немає ключа|api key/i
36
+
37
+ /**
38
+ * Класифікує помилку pi-agent-fix: systemic | transport | quality (замінює
39
+ * `classifyOmlxError` після pi-міграції — помилки приходять як винятки з `session.prompt`).
40
+ * @param {string|null|undefined} error повідомлення помилки
41
+ * @returns {'systemic'|'transport'|'quality'|null} клас
42
+ */
43
+ export function classifyFixError(error) {
44
+ if (!error) return null
45
+ // Наш агентний backstop-timeout — НЕ транспорт-збій провайдера: модель працювала, просто
46
+ // не встигла. Тому quality (а не transport-break) → драбина падає на наступний (сильніший)
47
+ // rung, замість обриву; для cloud-min це означає шанс cloud-avg.
48
+ if (/^fix timeout /i.test(error)) return 'quality'
49
+ if (SYSTEMIC_RE.test(error)) return 'systemic'
50
+ if (TRANSPORT_RE.test(error)) return 'transport'
51
+ return 'quality'
52
+ }
36
53
 
37
54
  /**
38
55
  * Будує драбину ескалації за наявними тирами (спека 2026-06-19-fix-escalation-cascade):
@@ -73,9 +90,9 @@ export function buildLadder({ localMin, cloudMin, cloudAvg }) {
73
90
  */
74
91
  function decideAfterFailure(rung, error) {
75
92
  if (!error) return null
76
- if (NO_KEY_RE.test(error)) return 'break'
77
- if (rung.local && classifyOmlxError(error) === 'systemic') return 'skip-model'
78
- if (!rung.local && CLOUD_TRANSPORT_RE.test(error)) return 'break'
93
+ const kind = classifyFixError(error)
94
+ if (kind === 'systemic') return rung.local ? 'skip-model' : 'break'
95
+ if (!rung.local && kind === 'transport') return 'break'
79
96
  return null
80
97
  }
81
98
 
@@ -88,7 +105,7 @@ function decideAfterFailure(rung, error) {
88
105
  * @param {string} cwd корінь проєкту
89
106
  * @param {{
90
107
  * ladder: Array<{tier:string,model:string,feedback:boolean,local:boolean,isAvg:boolean}>,
91
- * worker: { runLlmWorker: (ruleId: string, violation: string, cwd: string, opts: object) => object },
108
+ * worker: { runFix: (ruleId: string, violation: string, cwd: string, opts: object) => Promise<object> },
92
109
  * check: (rules: string[], cwd: string) => Promise<{rules: Array<{ruleId:string,ok:boolean,output:string}>}>,
93
110
  * avgBudget: number,
94
111
  * clock?: () => number,
@@ -126,42 +143,59 @@ export async function escalateRule(rule, cwd, deps) {
126
143
  }
127
144
 
128
145
  const startedAt = clock()
129
- const res = worker.runLlmWorker(rule.ruleId, currentViolation, cwd, {
146
+ // self_check (advisory §4+5) — той самий verdict-helper, що й зовнішній re-check.
147
+ const selfCheck = async () => {
148
+ const r = await check([rule.ruleId], cwd)
149
+ return { ok: r.rules.every(x => x.ok), output: r.rules.find(x => !x.ok)?.output ?? 'ok' }
150
+ }
151
+ const res = await worker.runFix(rule.ruleId, currentViolation, cwd, {
130
152
  model: rung.model,
153
+ tier: rung.tier,
131
154
  feedback: rung.feedback ? feedback : null,
132
155
  caller: `fix:${rule.ruleId}:${rung.tier}`,
133
- timeoutMs: rung.timeoutMs
156
+ timeoutMs: rung.timeoutMs,
157
+ deps: { selfCheck }
134
158
  })
135
159
  if (rung.isAvg) avgUsed++
136
160
 
161
+ // Зовнішній canonical re-check = джерело правди (§4+5).
137
162
  const recheck = await check([rule.ruleId], cwd)
138
163
  const recheckOk = recheck.rules.every(r => r.ok)
139
164
  const remaining = recheckOk ? '' : (recheck.rules.find(r => !r.ok)?.output ?? '')
165
+
166
+ // Distillation-стор §13: persist кожну спробу (повні edits + verdict).
167
+ if (res.telemetry) {
168
+ recordFixTelemetry({
169
+ ...res.telemetry,
170
+ violationSignature: currentViolation,
171
+ recheck: { external: recheckOk ? 'pass' : 'fail' },
172
+ escalated: !recheckOk
173
+ })
174
+ }
175
+
140
176
  record({
141
177
  ...common,
142
- callOk: res.ok,
178
+ callOk: !res.error,
143
179
  callError: res.error ?? null,
144
180
  recheckOk,
145
181
  remainingViolation: remaining,
146
- diagnosis: res.diagnosis ?? null,
182
+ diagnosis: res.telemetry ? `turns=${res.telemetry.turnCount} tools=${res.telemetry.toolCallCount}` : null,
147
183
  ms: clock() - startedAt
148
184
  })
149
185
 
150
186
  if (recheckOk) {
151
187
  log(` ✅ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}`)
152
- } else {
153
- const hint = res.error ? ` ❌ ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
154
- log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
188
+ return { resolved: true, avgUsed }
155
189
  }
156
190
 
157
- if (env.N_CURSOR_FIX_VERBOSE !== 'off' && res.promptSummary) {
158
- printVerboseBlock(rule.ruleId, res.promptSummary, res.reasoning ?? null, res.reasoningSource ?? null)
159
- }
191
+ const hint = res.error ? ` ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
192
+ log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
160
193
 
161
- if (recheckOk) return { resolved: true, avgUsed }
194
+ // Recheck провалився clean-slate per rung: відкотити правки цього рунга (§12).
195
+ res.rollback?.()
162
196
 
163
197
  // Feedback для наступного рунга + оновлений violation.
164
- feedback = { previousModel: rung.model, previousChanges: res.changes ?? [], previousError: res.error ?? null }
198
+ feedback = { previousModel: rung.model, previousError: res.error ?? null }
165
199
  currentViolation = remaining || currentViolation
166
200
 
167
201
  const action = decideAfterFailure(rung, res.error)
@@ -211,7 +245,7 @@ async function runT0Step(cwd, ruleFilter, failed) {
211
245
  * @returns {Promise<number>} 0 = all clean, 1 = unresolved
212
246
  */
213
247
  export async function runOrchestratorCli(args, cwd) {
214
- const worker = { runLlmWorker }
248
+ const worker = { runFix: runPiAgentFix }
215
249
  const { maxAvg, ruleFilter } = parseOrchestratorArgs(args)
216
250
  const ladder = buildLadder({ localMin: LOCAL_MIN, cloudMin: CLOUD_MIN, cloudAvg: CLOUD_AVG })
217
251
 
@@ -19,6 +19,15 @@ import { join } from 'node:path'
19
19
  /** Літерал безумовної автоактивації (українською, як у `auto-skills.mjs`). */
20
20
  export const SKILL_ALWAYS = 'завжди'
21
21
 
22
+ /** Допустимі тири моделі для агентного виконання скіла (`pi`-runner). */
23
+ export const SKILL_TIERS = ['min', 'avg', 'max']
24
+
25
+ /**
26
+ * Дефолтна тира за відсутності `main.json.tier`: скіли відкриті й агентні, слабка
27
+ * локальна модель іде в мета-рамблінг, тож безпечніше дефолтити в найсильніший тир.
28
+ */
29
+ export const DEFAULT_SKILL_TIER = 'max'
30
+
22
31
  /**
23
32
  * @typedef {{ always: true } | { rules: string[] }} SkillAutoSpec
24
33
  */
@@ -50,6 +59,19 @@ export function skillRequiresRoot(meta) {
50
59
  return meta?.worktree === true || meta?.requireRoot === true
51
60
  }
52
61
 
62
+ /**
63
+ * Тира моделі для агентного виконання скіла (`pi`-runner). Повертає `main.json.tier`,
64
+ * якщо це валідний тир, інакше — `DEFAULT_SKILL_TIER` (`max`).
65
+ * @param {Record<string, unknown> | null} meta розпарсений `main.json` (або null)
66
+ * @returns {'min'|'avg'|'max'} тира
67
+ */
68
+ export function skillTier(meta) {
69
+ const tier = meta?.tier
70
+ return typeof tier === 'string' && SKILL_TIERS.includes(tier)
71
+ ? /** @type {'min'|'avg'|'max'} */ (tier)
72
+ : DEFAULT_SKILL_TIER
73
+ }
74
+
53
75
  /**
54
76
  * Читає й парсить `main.json` одного скіла.
55
77
  * @param {string} skillDir абсолютний шлях до каталогу скіла
@@ -3,14 +3,16 @@
3
3
  *
4
4
  * Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
5
5
  * Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
6
- * `.n-cursor.json`) — далі stdout або делегування в `cursor-agent` / `claude`.
6
+ * `.n-cursor.json`) — далі stdout або виконання через вбудований pi-агент
7
+ * (рекомендовано), чи deprecated-делегування в `cursor-agent` / `claude`.
7
8
  *
8
9
  * Підтримувані формати:
9
10
  * `npx \@nitra/cursor skill list`
10
11
  * `npx \@nitra/cursor skill taze`
11
- * `npx \@nitra/cursor skill cursor taze`
12
- * `npx \@nitra/cursor skill cursor taze "онови залежності"`
13
- * `npx \@nitra/cursor skill claude taze` — те саме через Claude Code CLI
12
+ * `npx \@nitra/cursor skill pi taze` — виконати через вбудований pi-агент (рекомендовано)
13
+ * `npx \@nitra/cursor skill pi taze "онови залежності"`
14
+ * `npx \@nitra/cursor skill cursor taze` — deprecated: зовнішній Cursor CLI
15
+ * `npx \@nitra/cursor skill claude taze` — deprecated: зовнішній Claude Code CLI
14
16
  */
15
17
 
16
18
  import { spawnSync } from 'node:child_process'
@@ -19,14 +21,18 @@ import { dirname, join } from 'node:path'
19
21
  import { cwd } from 'node:process'
20
22
  import { fileURLToPath } from 'node:url'
21
23
 
22
- const RUNNERS = new Set(['cursor', 'claude'])
24
+ import { readSkillMetaRaw, skillTier } from './lib/skill-meta.mjs'
25
+
26
+ /** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`claude` — deprecated зовнішні CLI. */
27
+ const RUNNERS = new Set(['pi', 'cursor', 'claude'])
23
28
 
24
29
  const USAGE_LINES = [
25
30
  'Usage:',
26
31
  ' npx @nitra/cursor skill list',
27
32
  ' npx @nitra/cursor skill <skill-id> ["task"]',
28
- ' npx @nitra/cursor skill cursor <skill-id> ["task"]',
29
- ' npx @nitra/cursor skill claude <skill-id> ["task"]',
33
+ ' npx @nitra/cursor skill pi <skill-id> ["task"] # вбудований pi-агент (рекомендовано)',
34
+ ' npx @nitra/cursor skill cursor <skill-id> ["task"] # deprecated',
35
+ ' npx @nitra/cursor skill claude <skill-id> ["task"] # deprecated',
30
36
  '',
31
37
  'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
32
38
  ]
@@ -124,15 +130,43 @@ export function buildSkillPrompt(skillsRoot, rawSkillName, task, projectDir = cw
124
130
  }
125
131
 
126
132
  /**
133
+ * Виконує скіл через вбудований pi-агент (рекомендований шлях). Модель — з тиру скіла
134
+ * (`main.json.tier`, дефолт `max`); `cwd` = каталог виклику (worktree, за потреби,
135
+ * створює сам скіл за SKILL.md-preflight). Pi вантажиться lazy.
136
+ * @param {string} prompt готовий `buildSkillPrompt`
137
+ * @param {string} rawSkillName ім'я скілу з CLI (можливо з префіксом n-)
138
+ * @param {string} skillsRoot абсолютний шлях до `skills/` пакета
139
+ * @param {string} projectDir робочий каталог сесії (= каталог виклику)
140
+ * @param {(line: string) => void} logError вивід помилок
141
+ * @param {{ runPiAgentSkill?: Function }} deps інжекти для тестів
142
+ * @returns {Promise<number>} exit code (0 — ok)
143
+ */
144
+ async function runPiRunner(prompt, rawSkillName, skillsRoot, projectDir, logError, deps = {}) {
145
+ const skillId = normalizeSkillId(rawSkillName)
146
+ const tier = skillTier(readSkillMetaRaw(join(skillsRoot, skillId)))
147
+ const runPiAgentSkill = deps.runPiAgentSkill ?? (await import('../lib/pi-agent-skill.mjs')).runPiAgentSkill
148
+ const result = await runPiAgentSkill(prompt, { skillId, tier, cwd: projectDir })
149
+ if (result.error) {
150
+ logError(result.error)
151
+ }
152
+ return result.ok ? 0 : 1
153
+ }
154
+
155
+ /**
156
+ * Deprecated: делегує у зовнішній `claude -p` / `cursor-agent -p`. Лишається як fallback
157
+ * для тих, у кого pi-модель ще не налаштована; буде прибрано (мігруй на `skill pi`).
127
158
  * @param {'claude' | 'cursor'} kind який LLM CLI запускати
128
159
  * @param {string} prompt промпт для передачі у stdin
129
160
  * @param {string} projectDir робочий каталог дочірнього процесу
161
+ * @param {(line: string) => void} logError вивід попередження/помилок
130
162
  * @returns {number} exit code дочірнього процесу
131
163
  */
132
- function runLlmCli(kind, prompt, projectDir) {
164
+ function runLlmCli(kind, prompt, projectDir, logError) {
165
+ logError(`[deprecated] skill ${kind} → use 'skill pi'; зовнішній CLI буде прибрано`)
166
+
133
167
  if (kind === 'claude') {
134
168
  if (!isBinaryInPath('claude')) {
135
- throw new Error('`claude` not found in PATH. Install Claude Code CLI or use `skill cursor`.')
169
+ throw new Error('`claude` not found in PATH. Install Claude Code CLI or use `skill pi`.')
136
170
  }
137
171
 
138
172
  const result = spawnSync('claude', ['-p'], {
@@ -145,7 +179,7 @@ function runLlmCli(kind, prompt, projectDir) {
145
179
  }
146
180
 
147
181
  if (!isBinaryInPath('cursor-agent')) {
148
- throw new Error('`cursor-agent` not found in PATH. Install Cursor CLI or use `skill claude`.')
182
+ throw new Error('`cursor-agent` not found in PATH. Install Cursor CLI or use `skill pi`.')
149
183
  }
150
184
 
151
185
  const result = spawnSync('cursor-agent', ['-p'], {
@@ -168,15 +202,16 @@ export function resolveBundledPackageRoot(fromModuleUrl = import.meta.url) {
168
202
 
169
203
  /**
170
204
  * @param {string[]} argv аргументи після `skill` у `n-cursor`
171
- * @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options] перевизначення кореня пакета, каталогу проєкту та функцій виводу (для тестів)
172
- * @returns {number} exit code
205
+ * @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void, deps?: { runPiAgentSkill?: Function } }} [options] перевизначення кореня пакета, каталогу проєкту, функцій виводу та інжектів (для тестів)
206
+ * @returns {Promise<number>} exit code
173
207
  */
174
- export function runSkillsCli(argv, options = {}) {
208
+ export async function runSkillsCli(argv, options = {}) {
175
209
  const log = options.log ?? (line => console.log(line))
176
210
  const logError = options.logError ?? (line => console.error(line))
177
211
  const packageRoot = options.packageRoot ?? resolveBundledPackageRoot()
178
212
  const skillsRoot = join(packageRoot, 'skills')
179
213
  const projectDir = options.projectDir ?? cwd()
214
+ const deps = options.deps ?? {}
180
215
 
181
216
  const [first, second, ...rest] = argv
182
217
  const skillIds = listSkillIds(skillsRoot)
@@ -201,7 +236,10 @@ export function runSkillsCli(argv, options = {}) {
201
236
  }
202
237
  const task = rest.join(' ')
203
238
  const prompt = buildSkillPrompt(skillsRoot, second, task, projectDir)
204
- return runLlmCli(/** @type {'claude' | 'cursor'} */ (first), prompt, projectDir)
239
+ if (first === 'pi') {
240
+ return await runPiRunner(prompt, second, skillsRoot, projectDir, logError, deps)
241
+ }
242
+ return runLlmCli(/** @type {'claude' | 'cursor'} */ (first), prompt, projectDir, logError)
205
243
  }
206
244
 
207
245
  if (skillIds.includes(normalizeSkillId(first))) {
@@ -0,0 +1,105 @@
1
+ /** @see ./docs/ast-extract.md */
2
+
3
+ /**
4
+ * Generic AST-facts extractor для `ast_facts`-tool fix-engine (§3б спеки pi-migration).
5
+ *
6
+ * Дає агенту структурований зріз файлу (`imports` / `exports` / `topLevelFunctions`)
7
+ * замість сирого вмісту — скорочує redundant read-turns на слабкій локальній моделі.
8
+ * Використовується як **generic fallback**, коли правило не має власного
9
+ * `rules/<id>/js/_ast-context.mjs`.
10
+ *
11
+ * Парсинг — через спільний `parseProgramOrNull` (oxc). Будь-яка помилка (read/parse)
12
+ * деградує до `{ error, imports:[], exports:[], topLevelFunctions:[] }`, щоб агент
13
+ * продовжив із тим, що має (контракт §3б: fallback до голого вмісту).
14
+ */
15
+
16
+ import { readFileSync } from 'node:fs'
17
+ import { parseProgramOrNull } from './ast-scan-utils.mjs'
18
+
19
+ /** Порожній результат із причиною (read/parse fail). @param {string} error причина */
20
+ function empty(error) {
21
+ return { error, imports: [], exports: [], topLevelFunctions: [] }
22
+ }
23
+
24
+ /** Чи init-вираз — функція (для `export const f = () => …`). @param {unknown} node init */
25
+ function isFunctionInit(node) {
26
+ return !!node && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression')
27
+ }
28
+
29
+ /**
30
+ * Витягає AST-факти з вихідного коду (без файлового IO — для тестів і reuse).
31
+ * @param {string} content вихідний код
32
+ * @param {string} virtualPath шлях (визначає мову js/ts/jsx/tsx)
33
+ * @returns {{ imports: Array<{source: string, names: string[]}>, exports: string[], topLevelFunctions: string[], error?: string }} факти
34
+ */
35
+ export function extractContextFromSource(content, virtualPath) {
36
+ const program = parseProgramOrNull(content, virtualPath)
37
+ if (!program) return empty('parse failed')
38
+
39
+ const imports = []
40
+ const exports = []
41
+ const topLevelFunctions = []
42
+
43
+ for (const node of program.body ?? []) {
44
+ switch (node?.type) {
45
+ case 'ImportDeclaration':
46
+ imports.push({
47
+ source: node.source?.value ?? '',
48
+ names: (node.specifiers ?? []).map(s => s.local?.name).filter(Boolean)
49
+ })
50
+ break
51
+
52
+ case 'FunctionDeclaration':
53
+ if (node.id?.name) topLevelFunctions.push(node.id.name)
54
+ break
55
+
56
+ case 'ExportNamedDeclaration': {
57
+ const d = node.declaration
58
+ if (d?.type === 'FunctionDeclaration' && d.id?.name) {
59
+ exports.push(d.id.name)
60
+ topLevelFunctions.push(d.id.name)
61
+ } else if ((d?.type === 'ClassDeclaration' || d?.type === 'TSInterfaceDeclaration') && d.id?.name) {
62
+ exports.push(d.id.name)
63
+ } else if (d?.type === 'VariableDeclaration') {
64
+ for (const decl of d.declarations ?? []) {
65
+ if (!decl.id?.name) continue
66
+ exports.push(decl.id.name)
67
+ if (isFunctionInit(decl.init)) topLevelFunctions.push(decl.id.name)
68
+ }
69
+ }
70
+ for (const spec of node.specifiers ?? []) {
71
+ if (spec.exported?.name) exports.push(spec.exported.name)
72
+ }
73
+ break
74
+ }
75
+
76
+ case 'ExportDefaultDeclaration':
77
+ exports.push('default')
78
+ break
79
+
80
+ case 'ExportAllDeclaration':
81
+ exports.push(node.exported?.name ?? '*')
82
+ break
83
+
84
+ default:
85
+ break
86
+ }
87
+ }
88
+
89
+ return { imports, exports, topLevelFunctions }
90
+ }
91
+
92
+ /**
93
+ * Читає файл і витягає AST-факти. Read-помилка → `{ error, … }` (не кидає).
94
+ * @param {string} filePath абсолютний/відносний шлях до файлу
95
+ * @returns {{ imports: Array<{source: string, names: string[]}>, exports: string[], topLevelFunctions: string[], error?: string }} факти
96
+ */
97
+ export function extractContext(filePath) {
98
+ let content
99
+ try {
100
+ content = readFileSync(filePath, 'utf8')
101
+ } catch (e) {
102
+ return empty(`read failed: ${e.message}`)
103
+ }
104
+ return extractContextFromSource(content, filePath)
105
+ }
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: JS Module
3
+ title: ast-extract.mjs
4
+ resource: npm/scripts/utils/ast-extract.mjs
5
+ docgen:
6
+ crc: 61e8c7bc
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей модуль аналізує вихідний код, витягуючи ключові структурні елементи. Він дозволяє отримати інформацію про імпорти, експорти та назви верхнерівневих функцій, використовуючи функції `f`, `extractContextFromSource` та `extractContext`. Функціонал працює з кодом, наданим безпосередньо або шляхом до файлу. Процес аналізу реалізований з механізмом перехоплення помилок (fail-safe), гарантуючи, що винятки не будуть викинуті назовні.
14
+
15
+ ## Поведінка
16
+
17
+ f: витягує структурований зріз коду, включаючи імпорти, експорти та назви верхнерівневих функцій.
18
+ extractContextFromSource: витягує структурований зріз коду з наданого вмісту, визначуючи імпорти, експорти та назви верхнерівневих функцій.
19
+ extractContext: читає вміст файлу за вказаним шляхом і витягує з нього структуровані факти коду, обробляючи помилки під час читання.
20
+
21
+ ## Публічний API
22
+
23
+ - f — основна логіка модуля
24
+ - extractContextFromSource — аналізує структуру коду, наданого як текст, для отримання його граматичного представлення, не звертаючись до файлової системи
25
+ - extractContext — читає вміст файлу та аналізує його структуру, повертаючи помилку читання замість помилки виконання
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
30
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
package/lib/docs/llm.md DELETED
@@ -1,33 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: llm.mjs
4
- resource: npm/lib/llm.mjs
5
- docgen:
6
- crc: 22d2906f
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- ---
10
-
11
- Файл є єдиною точкою LLM-викликів для JS-оркестраторів (див. ADR 260610-2228). Він маршрутизує запити виключно за префіксом `model-id` (конвенція `npm/lib/models.mjs`): `omlx/<model>` — прямий HTTP до локального omlx-сервера (`callOmlx`), будь-що інше — через `pi` CLI (хмарні провайдери або pi-дефолт). Рядок моделі сам визначає транспорт; відсутні env-перемикачі бекенду. Кожен виклик має **always-on** багатий JSONL-запис трасування (reasoning + слід). Для `omlx` захоплюються `content/reasoning/usage/finish_reason/attempts`, для `pi` — лише те, що надає CLI (rich-поля null). Код спирається на конфігурації, визначені у settings.json.
12
-
13
- ## Поведінка
14
-
15
- pickBackend визначає, чи має бути використаний локальний omlx-сервер для виклику LLM, чи зовнішній CLI `pi`, на основі префікса `model-id`.
16
- callLlmRich виконує універсальний виклик LLM з маршрутизацією між omlx та `pi` CLI, завжди записуючи детальний слід (wire-trace) для обох каналів, і повертає вміст та монолог обробки.
17
- callLlm повертає лише текстовий вміст відповіді від LLM, використовуючи `callLlmRich` як внутрішню обгортку.
18
- classifyOmlxError класифікує помилку, отриману від omlx-сервера, як тимчасову, системну чи постійну, для прийняття рішення оркестратором.
19
- omlxHealthCheck виконує мінімальний перевірочний виклик до omlx-сервера для визначення його стану (працює, потребує ключа, перевантажений).
20
- preflightLocalModel перевіряє стан локальної omlx-моделі, щоб визначити, чи можна безпечно ініціювати виклик, надаючи зрозуміле повідомлення про проблеми з пам'яттю, автентифікацією чи доступністю сервера.
21
-
22
- ## Публічний API
23
-
24
- pickBackend — Визначає, який механізм зв'язку використовувати для моделі: прямий HTTP або CLI.
25
- callLlmRich — Виконує універсальний запит до LLM, маршрутизуючи його за префіксом моделі та відстежуючи трасування, повертаючи детальний об'єкт з відповіддю та логікою обґрунтування.
26
- callLlm — Викликає LLM, повертаючи лише текстовий вміст відповіді, зберігаючи сумісність зі старими споживачами.
27
- classifyOmlxError — Аналізує помилки omlx після вичерпання внутрішніх спроб, щоб визначити, чи потрібно ігнорувати, зупинити роботу (circuit-breaker) чи обробляти як звичайну помилку.
28
- omlxHealthCheck — Перевіряє готовність omlx-сервера мінімальним запитом, щоб визначити його стан: відсутність відповіді, обмеження пам'яті, відсутність ключа або інша помилка.
29
- preflightLocalModel — Перевіряє можливість використання локальної моделі для швидкого пропуску викликів, використовуючи файли документації та словники.
30
-
31
- ## Гарантії поведінки
32
-
33
- - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,48 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: models.mjs
4
- resource: npm/lib/models.mjs
5
- docgen:
6
- crc: 181e2bf9
7
- score: 100
8
- ---
9
-
10
- Файл визначає глобальну класифікацію моделей для pi. Конфігурація встановлюється через змінні середовища (наприклад, `N_LOCAL_MIN_MODEL`) та використовується для визначення, який провайдер буде викликаний. Кожен скіл посилається на потрібний тир, який використовується для вибору моделі.
11
-
12
- ## Поведінка
13
-
14
- LOCAL_MIN
15
- Завантажує модель для мінімального локального виведення
16
-
17
- LOCAL_AVG
18
- Завантажує модель для середнього локального виведення
19
-
20
- LOCAL_MAX
21
- Завантажує модель для максимального локального виведення
22
-
23
- CLOUD_MIN
24
- Завантажує модель для мінімального хмарного виведення
25
-
26
- CLOUD_AVG
27
- Завантажує модель для середнього хмарного виведення
28
-
29
- CLOUD_MAX
30
- Завантажує модель для максимального хмарного виведення
31
-
32
- resolveModel
33
- Повертає перший непорожній model-id для запитаного тиру, каскадно перевіряючи локальні тири, а тоді хмарний еквівалент
34
-
35
- ## Публічний API
36
-
37
- LOCAL_MIN — Швидкий локальний inference. Напр.: ollama/gemma3:4b
38
- LOCAL_AVG — Середній локальний. Напр.: ollama/gemma4:26b-moe
39
- LOCAL_MAX — Максимальний локальний. Напр.: ollama/llama4-maverick
40
- CLOUD_MIN — Мінімальний хмарний. Напр.: openai/gpt-5.4-mini, google/gemini-2.5-flash, anthropic/claude-haiku-4-5
41
- CLOUD_AVG — Середній хмарний. Напр.: openai/gpt-5.4, google/gemini-2.5-pro, anthropic/claude-sonnet-4-6
42
- CLOUD_MAX — Максимальний хмарний. Напр.: openai/gpt-5.5, anthropic/claude-opus-4-8
43
- resolveModel — Знаходить перший непорожній model-id для запиту, починаючи з локальних, а потім переходить до хмарних варіантів.
44
-
45
- ## Гарантії поведінки
46
-
47
- - Read-only: файл не виконує операцій запису у файлову систему.
48
- - Не звертається до мережі.