@nitra/cursor 12.15.0 → 12.16.0
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 +2 -11
- 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/bun/docs/main.md +7 -6
- 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/python/docs/main.md +11 -11
- package/rules/rust/docs/main.md +5 -5
- 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/docs/update-blue-oak.md +8 -8
- 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/discover-checkable-rules.md +6 -6
- package/scripts/lib/docs/inline-template-links.md +8 -6
- package/scripts/lib/docs/list-project-rules-mdc.md +5 -3
- package/scripts/lib/docs/root-notice.md +13 -16
- package/scripts/lib/docs/run-lint.md +10 -8
- package/scripts/lib/docs/skill-meta.md +29 -10
- package/scripts/lib/fix/docs/discover-t0-patterns.md +10 -13
- package/scripts/lib/fix/docs/escalation-log.md +10 -9
- package/scripts/lib/fix/docs/index.md +0 -1
- package/scripts/lib/fix/docs/orchestrator.md +15 -13
- package/scripts/lib/fix/escalation-log.mjs +1 -1
- package/scripts/lib/fix/orchestrator.mjs +67 -32
- package/scripts/lib/run-lint.mjs +2 -10
- 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/scripts/utils/docs/walkDir.md +17 -20
- 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/analyze-escalation.mjs +0 -353
- package/scripts/lib/fix/docs/analyze-escalation.md +0 -44
- 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 -33
- 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 -332
- package/scripts/lib/fix/verbose-block.mjs +0 -82
|
@@ -3,29 +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
|
+
|
|
13
|
+
Цей модуль керує процесом виправлення порушень. Він виконує функцію `parseOrchestratorArgs` для визначення обсягу роботи з хмарними моделями та фільтрами правил. Модуль забезпечує класифікацію помилок через `classifyFixError` та ескалацію правил за допомогою `escalateRule`. Крім того, він створює структуру для застосування правил за допомогою `buildLadder` і запускає основний клієнтський процес за допомогою `runOrchestratorCli`.
|
|
12
14
|
|
|
13
15
|
## Поведінка
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Поведінка:
|
|
18
|
+
classifyFixError визначає тип помилки, виниклої при спробі виправлення.
|
|
19
|
+
buildLadder конструює послідовність моделей для багаторівневої спроби усунення порушень.
|
|
20
|
+
escalateRule ітерує по моделях драбини, намагаючись виправити одне порушення, і звітує про результат.
|
|
21
|
+
parseOrchestratorArgs виділяє максимальну кількість викликів хмарної моделі та фільтр правил із вхідних аргументів.
|
|
22
|
+
runOrchestratorCli виконує повний цикл фіксації порушень, включаючи початкову перевірку, детерміноване виправлення та ітерації з LLM.
|
|
19
23
|
|
|
20
24
|
## Публічний API
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
escalateRule —
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
runOrchestratorCli — Запускає основний процес оркестрації, обробляючи аргументи та керуючи виконанням правил.
|
|
26
|
+
classifyFixError — Визначає тип помилки, яку виявив pi-agent-fix: системна, транспортна чи якісна.
|
|
27
|
+
buildLadder — Створює послідовність моделей для усунення помилок, починаючи з найменш ресурсомістких локальних спроб і рухаючись до хмарних моделей, враховуючи попередній досвід.
|
|
28
|
+
escalateRule — Застосовує одне правило за визначеною драбиною, повторюючи перевірку після кожного кроку та фіксуючи результати у лозі.
|
|
29
|
+
parseOrchestratorArgs — Зчитує аргументи командного рядка, зокрема максимальний бюджет середнього рівня, та витягує фільтр правил.
|
|
30
|
+
runOrchestratorCli — Виконує основну логіку оркестрації, керуючи циклом виявлення, класифікації та ескалації виправлень.
|
|
29
31
|
|
|
30
32
|
## Гарантії поведінки
|
|
31
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,22 +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.
|
|
23
|
+
* Перевизначення: `N_LOCAL_FIX_TIMEOUT_MS` / `N_CLOUD_FIX_TIMEOUT_MS`.
|
|
22
24
|
*/
|
|
23
|
-
const LOCAL_TIMEOUT_MS = Number(env.N_LOCAL_FIX_TIMEOUT_MS) ||
|
|
24
|
-
const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) ||
|
|
25
|
+
const LOCAL_TIMEOUT_MS = Number(env.N_LOCAL_FIX_TIMEOUT_MS) || 300_000
|
|
26
|
+
const CLOUD_TIMEOUT_MS = Number(env.N_CLOUD_FIX_TIMEOUT_MS) || 300_000
|
|
25
27
|
|
|
26
|
-
/**
|
|
27
|
-
const
|
|
28
|
+
/** Реальний транспорт-збій провайдера (мережа/сокет) — НЕ наш агентний backstop-timeout. */
|
|
29
|
+
const TRANSPORT_RE = /etimedout|timed out|econnrefused|connection refused/i
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* модель, не більший timeout. Ескалація на неї лише спалить avg-бюджет → обрив.
|
|
32
|
+
* Systemic — повтор тієї ж моделі марний: нема git, fail-closed guard, відсутня модель,
|
|
33
|
+
* registry/session/auth. Quality — модель видала поганий фікс (retry/escalate може допомогти).
|
|
33
34
|
*/
|
|
34
|
-
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
|
+
}
|
|
35
53
|
|
|
36
54
|
/**
|
|
37
55
|
* Будує драбину ескалації за наявними тирами (спека 2026-06-19-fix-escalation-cascade):
|
|
@@ -72,9 +90,9 @@ export function buildLadder({ localMin, cloudMin, cloudAvg }) {
|
|
|
72
90
|
*/
|
|
73
91
|
function decideAfterFailure(rung, error) {
|
|
74
92
|
if (!error) return null
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
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'
|
|
78
96
|
return null
|
|
79
97
|
}
|
|
80
98
|
|
|
@@ -87,7 +105,7 @@ function decideAfterFailure(rung, error) {
|
|
|
87
105
|
* @param {string} cwd корінь проєкту
|
|
88
106
|
* @param {{
|
|
89
107
|
* ladder: Array<{tier:string,model:string,feedback:boolean,local:boolean,isAvg:boolean}>,
|
|
90
|
-
* worker: {
|
|
108
|
+
* worker: { runFix: (ruleId: string, violation: string, cwd: string, opts: object) => Promise<object> },
|
|
91
109
|
* check: (rules: string[], cwd: string) => Promise<{rules: Array<{ruleId:string,ok:boolean,output:string}>}>,
|
|
92
110
|
* avgBudget: number,
|
|
93
111
|
* clock?: () => number,
|
|
@@ -125,42 +143,59 @@ export async function escalateRule(rule, cwd, deps) {
|
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
const startedAt = clock()
|
|
128
|
-
|
|
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, {
|
|
129
152
|
model: rung.model,
|
|
153
|
+
tier: rung.tier,
|
|
130
154
|
feedback: rung.feedback ? feedback : null,
|
|
131
155
|
caller: `fix:${rule.ruleId}:${rung.tier}`,
|
|
132
|
-
timeoutMs: rung.timeoutMs
|
|
156
|
+
timeoutMs: rung.timeoutMs,
|
|
157
|
+
deps: { selfCheck }
|
|
133
158
|
})
|
|
134
159
|
if (rung.isAvg) avgUsed++
|
|
135
160
|
|
|
161
|
+
// Зовнішній canonical re-check = джерело правди (§4+5).
|
|
136
162
|
const recheck = await check([rule.ruleId], cwd)
|
|
137
163
|
const recheckOk = recheck.rules.every(r => r.ok)
|
|
138
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
|
+
|
|
139
176
|
record({
|
|
140
177
|
...common,
|
|
141
|
-
callOk: res.
|
|
178
|
+
callOk: !res.error,
|
|
142
179
|
callError: res.error ?? null,
|
|
143
180
|
recheckOk,
|
|
144
181
|
remainingViolation: remaining,
|
|
145
|
-
diagnosis: res.
|
|
182
|
+
diagnosis: res.telemetry ? `turns=${res.telemetry.turnCount} tools=${res.telemetry.toolCallCount}` : null,
|
|
146
183
|
ms: clock() - startedAt
|
|
147
184
|
})
|
|
148
185
|
|
|
149
186
|
if (recheckOk) {
|
|
150
187
|
log(` ✅ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}`)
|
|
151
|
-
|
|
152
|
-
const hint = res.error ? ` ❌ ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
|
|
153
|
-
log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
|
|
188
|
+
return { resolved: true, avgUsed }
|
|
154
189
|
}
|
|
155
190
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
191
|
+
const hint = res.error ? ` ❌ ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
|
|
192
|
+
log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
|
|
159
193
|
|
|
160
|
-
|
|
194
|
+
// Recheck провалився → clean-slate per rung: відкотити правки цього рунга (§12).
|
|
195
|
+
res.rollback?.()
|
|
161
196
|
|
|
162
197
|
// Feedback для наступного рунга + оновлений violation.
|
|
163
|
-
feedback = { previousModel: rung.model,
|
|
198
|
+
feedback = { previousModel: rung.model, previousError: res.error ?? null }
|
|
164
199
|
currentViolation = remaining || currentViolation
|
|
165
200
|
|
|
166
201
|
const action = decideAfterFailure(rung, res.error)
|
|
@@ -210,7 +245,7 @@ async function runT0Step(cwd, ruleFilter, failed) {
|
|
|
210
245
|
* @returns {Promise<number>} 0 = all clean, 1 = unresolved
|
|
211
246
|
*/
|
|
212
247
|
export async function runOrchestratorCli(args, cwd) {
|
|
213
|
-
const worker = {
|
|
248
|
+
const worker = { runFix: runPiAgentFix }
|
|
214
249
|
const { maxAvg, ruleFilter } = parseOrchestratorArgs(args)
|
|
215
250
|
const ladder = buildLadder({ localMin: LOCAL_MIN, cloudMin: CLOUD_MIN, cloudAvg: CLOUD_AVG })
|
|
216
251
|
|
package/scripts/lib/run-lint.mjs
CHANGED
|
@@ -166,22 +166,14 @@ async function runPerFileRules(ids, ctx) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Конформність-фаза `--full
|
|
170
|
-
* (записи саме цього прогону), у fix-режимі по конформності викликає аналіз.
|
|
169
|
+
* Конформність-фаза `--full`.
|
|
171
170
|
* @param {string} cwd корінь
|
|
172
171
|
* @param {boolean} readOnly лише детект
|
|
173
172
|
* @param {(s: string) => void} log логер
|
|
174
173
|
* @returns {Promise<number>} код конформності
|
|
175
174
|
*/
|
|
176
175
|
async function runFullConformancePhase(cwd, readOnly, log) {
|
|
177
|
-
|
|
178
|
-
const escOffset = readOnly ? 0 : escalationLogSize()
|
|
179
|
-
const conformanceCode = await runConformance(cwd, readOnly, log)
|
|
180
|
-
if (!readOnly) {
|
|
181
|
-
reportRunStats(escOffset, log) // резюме викликів моделей (локальна / cloud-min / cloud-avg)
|
|
182
|
-
maybeAnalyzeEscalation(cwd, escOffset, log)
|
|
183
|
-
}
|
|
184
|
-
return conformanceCode
|
|
176
|
+
return runConformance(cwd, readOnly, log)
|
|
185
177
|
}
|
|
186
178
|
|
|
187
179
|
/**
|
|
@@ -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).
|
|
@@ -3,34 +3,31 @@ type: JS Module
|
|
|
3
3
|
title: walkDir.mjs
|
|
4
4
|
resource: npm/scripts/utils/walkDir.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 73503ca2
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
7
9
|
---
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Огляд
|
|
12
|
+
|
|
13
|
+
Публічна функція `walkDir` здійснює рекурсивний пошук файлів у вказаному каталозі. Вона обходить файлову систему, ігноруючи каталоги `.git` та `node_modules`. Знаходячи кожен файл, вона передає його повний шлях у колбек для подальшої обробки. Компонент є read-only, тобто не виконує записів у файлову систему чи бази даних. При роботі з файловою системою він перехоплює помилки, забезпечуючи безпечну роботу без викидання винятків.
|
|
10
14
|
|
|
11
15
|
## Поведінка
|
|
12
16
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
4. Якщо при спробі `readdir` для каталогу виникає помилка, обхід цього каталогу припиняється.
|
|
21
|
-
5. Обхід завершується, коли всі підкаталоги та файли в заданому дереві були оброблені.
|
|
17
|
+
1. Викликається функція walkDir для початку рекурсивного обходу каталогу.
|
|
18
|
+
2. Система розшифровує вхідний шлях до кореня обходу.
|
|
19
|
+
3. Система формує додаткові правила ігнорування на основі наданих шляхів.
|
|
20
|
+
4. Система завжди ігнорує каталоги .git та node_modules.
|
|
21
|
+
5. Система виконує пошук усіх файлів у каталозі, застосовуючи правила ігнорування, включаючи ті, що визначені в .gitignore.
|
|
22
|
+
6. У разі виникнення помилки під час пошуку, процес обходу припиняється без генерації винятку.
|
|
23
|
+
7. Для кожного знайденого файлу система викликає наданий колбек, передаючи йому повний абсолютний шлях до цього файлу.
|
|
22
24
|
|
|
23
25
|
## Публічний API
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
walkDir — рекурсивно переглядає файли та папки, ігноруючи ті, що вказані у файлі `.gitignore`.
|
|
26
28
|
|
|
27
29
|
## Гарантії поведінки
|
|
28
30
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
- Може пропустити каталоги, вказані в `ignorePaths`.
|
|
33
|
-
- У разі невдачі `readdir` для каталогу, функція тихо виходить без викидання помилок.
|
|
34
|
-
- Функція не викидає винятки назовні.
|
|
35
|
-
- У разі невдачі повертає `false` або `null`.
|
|
36
|
-
- Не використовує кешування.
|
|
31
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
32
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
33
|
+
- Свідомо пропускає шляхи: `.git`, `node_modules`.
|