@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
|
@@ -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:
|
|
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
|
-
|
|
13
|
+
Цей модуль керує процесом виправлення порушень. Він виконує функцію `parseOrchestratorArgs` для визначення обсягу роботи з хмарними моделями та фільтрами правил. Модуль забезпечує класифікацію помилок через `classifyFixError` та ескалацію правил за допомогою `escalateRule`. Крім того, він створює структуру для застосування правил за допомогою `buildLadder` і запускає основний клієнтський процес за допомогою `runOrchestratorCli`.
|
|
14
14
|
|
|
15
15
|
## Поведінка
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Поведінка:
|
|
18
|
+
classifyFixError визначає тип помилки, виниклої при спробі виправлення.
|
|
19
|
+
buildLadder конструює послідовність моделей для багаторівневої спроби усунення порушень.
|
|
20
|
+
escalateRule ітерує по моделях драбини, намагаючись виправити одне порушення, і звітує про результат.
|
|
21
|
+
parseOrchestratorArgs виділяє максимальну кількість викликів хмарної моделі та фільтр правил із вхідних аргументів.
|
|
22
|
+
runOrchestratorCli виконує повний цикл фіксації порушень, включаючи початкову перевірку, детерміноване виправлення та ітерації з LLM.
|
|
21
23
|
|
|
22
24
|
## Публічний API
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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/
|
|
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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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) ||
|
|
26
|
+
const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) || 300_000
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
const
|
|
28
|
+
/** Реальний транспорт-збій провайдера (мережа/сокет) — НЕ наш агентний backstop-timeout. */
|
|
29
|
+
const TRANSPORT_RE = /etimedout|timed out|econnrefused|connection refused/i
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* модель, не більший timeout. Ескалація на неї лише спалить avg-бюджет → обрив.
|
|
32
|
+
* Systemic — повтор тієї ж моделі марний: нема git, fail-closed guard, відсутня модель,
|
|
33
|
+
* registry/session/auth. Quality — модель видала поганий фікс (retry/escalate може допомогти).
|
|
34
34
|
*/
|
|
35
|
-
const
|
|
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
|
-
|
|
77
|
-
if (
|
|
78
|
-
if (!rung.local &&
|
|
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: {
|
|
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
|
-
|
|
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.
|
|
178
|
+
callOk: !res.error,
|
|
143
179
|
callError: res.error ?? null,
|
|
144
180
|
recheckOk,
|
|
145
181
|
remainingViolation: remaining,
|
|
146
|
-
diagnosis: res.
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
191
|
+
const hint = res.error ? ` ❌ ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
|
|
192
|
+
log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
|
|
160
193
|
|
|
161
|
-
|
|
194
|
+
// Recheck провалився → clean-slate per rung: відкотити правки цього рунга (§12).
|
|
195
|
+
res.rollback?.()
|
|
162
196
|
|
|
163
197
|
// Feedback для наступного рунга + оновлений violation.
|
|
164
|
-
feedback = { previousModel: rung.model,
|
|
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 = {
|
|
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 абсолютний шлях до каталогу скіла
|
package/scripts/skills-cli.mjs
CHANGED
|
@@ -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 або
|
|
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
|
|
12
|
-
* `npx \@nitra/cursor skill
|
|
13
|
-
* `npx \@nitra/cursor skill
|
|
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
|
-
|
|
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
|
|
29
|
-
' npx @nitra/cursor skill
|
|
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
|
|
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
|
|
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
|
-
|
|
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: не виконує операцій запису (ФС/БД).
|
package/lib/docs/models.md
DELETED
|
@@ -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
|
-
- Не звертається до мережі.
|