@nitra/cursor 3.26.0 → 3.27.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 +7 -0
- package/bin/n-cursor.js +29 -9
- package/package.json +1 -1
- package/rules/ga/docs/fix.md +16 -149
- package/rules/ga/js/docs/lint.md +12 -93
- package/rules/ga/js/docs/workflows.md +28 -213
- package/rules/ga/lint/docs/lint.md +24 -206
- package/skills/docgen/js/docgen-gen.mjs +212 -11
- package/skills/docgen/js/docgen-ignore.mjs +3 -1
- package/skills/docgen/js/docgen-prompts.mjs +7 -1
- package/skills/fix/SKILL.md +5 -31
- package/skills/fix/js/llm-worker.mjs +181 -0
- package/skills/fix/js/orchestrator.mjs +128 -0
- package/skills/fix/js/t0.mjs +229 -0
- package/skills/fix/meta.json +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T0-auto: детермінований рівень виправлень для n-fix оркестратора.
|
|
3
|
+
*
|
|
4
|
+
* Парсить `output` з `n-cursor fix --json` → застосовує програмний фікс без LLM.
|
|
5
|
+
* Умова застосування: violation-message містить конкретне цільове значення
|
|
6
|
+
* (назву файлу, рядок для вставки, ім'я залежності), яке можна видобути regex.
|
|
7
|
+
*
|
|
8
|
+
* Ієрархія: T0 (rm/create, знаний тип) → T0-auto (parse violation) → T1 (LLM).
|
|
9
|
+
* T0-auto запускається першим у конвергентному циклі; T1 — лише для решти.
|
|
10
|
+
*
|
|
11
|
+
* Публічний API:
|
|
12
|
+
* applyT0Auto(ruleId, violationOutput, cwd) → { applied: boolean, actions: string[] }
|
|
13
|
+
* listT0AutoRules() → string[] (ids що мають хоч один паттерн)
|
|
14
|
+
* runT0AutoCli(args, cwd) → Promise<number> (exit 0=clean, 1=violation)
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
import { spawnSync } from 'node:child_process'
|
|
19
|
+
import { dirname } from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Патерни T0-auto.
|
|
24
|
+
* Кожен паттерн: {
|
|
25
|
+
* id: string — унікальна назва паттерну (для логу)
|
|
26
|
+
* test: (output)=>bool — чи підходить цей output до паттерну
|
|
27
|
+
* apply: (match, cwd)=>{ ok: bool, action: string } — застосувати фікс
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
const PATTERNS = [
|
|
31
|
+
// ── vscode-ext-add ──────────────────────────────────────────────────────────
|
|
32
|
+
// Violation: «recommendations має містити "tsandall.opa"»
|
|
33
|
+
// Fix: додати рядок у .vscode/extensions.json#recommendations
|
|
34
|
+
{
|
|
35
|
+
id: 'vscode-ext-add',
|
|
36
|
+
test: out => /recommendations має містити "[^"]+"/.test(out),
|
|
37
|
+
apply: (out, cwd) => {
|
|
38
|
+
const matches = [...out.matchAll(/recommendations має містити "([^"]+)"/g)]
|
|
39
|
+
if (matches.length === 0) return { ok: false, action: 'no match' }
|
|
40
|
+
|
|
41
|
+
const extPath = join(cwd, '.vscode/extensions.json')
|
|
42
|
+
if (!existsSync(extPath)) {
|
|
43
|
+
return { ok: false, action: '.vscode/extensions.json не знайдено' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let parsed
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(readFileSync(extPath, 'utf8'))
|
|
49
|
+
} catch {
|
|
50
|
+
return { ok: false, action: '.vscode/extensions.json: невалідний JSON' }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const recs = Array.isArray(parsed.recommendations) ? parsed.recommendations : []
|
|
54
|
+
const toAdd = matches.map(m => m[1]).filter(e => !recs.includes(e))
|
|
55
|
+
if (toAdd.length === 0) return { ok: false, action: 'вже є' }
|
|
56
|
+
|
|
57
|
+
parsed.recommendations = [...recs, ...toAdd]
|
|
58
|
+
writeFileSync(extPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8')
|
|
59
|
+
return { ok: true, action: `додано до extensions.json: ${toAdd.join(', ')}` }
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// ── rm-forbidden-file ────────────────────────────────────────────────────────
|
|
64
|
+
// Violation: «Знайдено заборонений файл: package-lock.json»
|
|
65
|
+
// Fix: видалити файл
|
|
66
|
+
{
|
|
67
|
+
id: 'rm-forbidden-file',
|
|
68
|
+
test: out => /Знайдено заборонений файл: \S+/.test(out),
|
|
69
|
+
apply: (out, cwd) => {
|
|
70
|
+
const matches = [...out.matchAll(/Знайдено заборонений файл: (\S+)/g)]
|
|
71
|
+
if (matches.length === 0) return { ok: false, action: 'no match' }
|
|
72
|
+
|
|
73
|
+
const removed = []
|
|
74
|
+
for (const m of matches) {
|
|
75
|
+
const filePath = join(cwd, m[1])
|
|
76
|
+
if (existsSync(filePath)) {
|
|
77
|
+
rmSync(filePath, { force: true })
|
|
78
|
+
removed.push(m[1])
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (removed.length === 0) return { ok: false, action: 'файлів не знайдено' }
|
|
82
|
+
return { ok: true, action: `видалено: ${removed.join(', ')}` }
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Застосовує всі T0-auto паттерни до одного violation-output.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} ruleId id правила (для логу)
|
|
91
|
+
* @param {string} violationOutput рядок з поля `output` у `fix --json`
|
|
92
|
+
* @param {string} cwd корінь проєкту
|
|
93
|
+
* @returns {{ applied: boolean, actions: string[] }}
|
|
94
|
+
*/
|
|
95
|
+
export function applyT0Auto(ruleId, violationOutput, cwd) {
|
|
96
|
+
const actions = []
|
|
97
|
+
let applied = false
|
|
98
|
+
|
|
99
|
+
for (const p of PATTERNS) {
|
|
100
|
+
if (!p.test(violationOutput)) continue
|
|
101
|
+
const result = p.apply(violationOutput, cwd)
|
|
102
|
+
actions.push(`[${p.id}] ${result.action}`)
|
|
103
|
+
if (result.ok) {
|
|
104
|
+
applied = true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { applied, actions }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Повертає список id правил, для яких є хоча б один T0-auto паттерн
|
|
113
|
+
* (визначається по violation-output із `fix --json`).
|
|
114
|
+
*
|
|
115
|
+
* @param {{ ruleId: string, output: string }[]} failedRules
|
|
116
|
+
* @returns {string[]}
|
|
117
|
+
*/
|
|
118
|
+
export function filterT0AutoRules(failedRules) {
|
|
119
|
+
return failedRules
|
|
120
|
+
.filter(r => PATTERNS.some(p => p.test(r.output)))
|
|
121
|
+
.map(r => r.ruleId)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── CLI entry-point ──────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
127
|
+
/** Абсолютний шлях до npm/bin/n-cursor.js відносно цього файлу */
|
|
128
|
+
const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* CLI підкоманда `n-cursor fix-t0 [rule...]`.
|
|
132
|
+
* Запускає `fix --json`, застосовує T0-auto для кожного violation,
|
|
133
|
+
* повторно перевіряє check-gate, виводить підсумок.
|
|
134
|
+
*
|
|
135
|
+
* @param {string[]} args аргументи підкоманди (опційний список rule-ids)
|
|
136
|
+
* @param {string} cwd корінь проєкту
|
|
137
|
+
* @returns {Promise<number>} 0 — T0-auto закрив всі або немає порушень; 1 — лишились
|
|
138
|
+
*/
|
|
139
|
+
export async function runT0AutoCli(args, cwd) {
|
|
140
|
+
const ruleFilter = args.filter(a => !a.startsWith('--'))
|
|
141
|
+
const verbose = args.includes('--verbose') || args.includes('-v')
|
|
142
|
+
|
|
143
|
+
// 1. Запустити fix --json
|
|
144
|
+
const fixResult = spawnSync(
|
|
145
|
+
'bun',
|
|
146
|
+
[N_CURSOR_BIN, '_fix-check', ...ruleFilter],
|
|
147
|
+
{ cwd, encoding: 'utf8', timeout: 120_000 }
|
|
148
|
+
)
|
|
149
|
+
const raw = fixResult.stdout?.trim()
|
|
150
|
+
if (!raw) {
|
|
151
|
+
console.error(`n-cursor fix-t0: fix --json повернув порожній stdout`)
|
|
152
|
+
console.error(fixResult.stderr?.slice(0, 300) ?? '')
|
|
153
|
+
return 1
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let fixJson
|
|
157
|
+
try {
|
|
158
|
+
fixJson = JSON.parse(raw)
|
|
159
|
+
} catch {
|
|
160
|
+
console.error(`n-cursor fix-t0: fix --json повернув невалідний JSON`)
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const failed = fixJson.rules.filter(r => !r.ok)
|
|
165
|
+
if (failed.length === 0) {
|
|
166
|
+
console.log(`✅ fix-t0: всі правила чисті — T0 не потрібен`)
|
|
167
|
+
return 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 2. Застосувати T0-auto
|
|
171
|
+
const applied = []
|
|
172
|
+
const skipped = []
|
|
173
|
+
for (const r of failed) {
|
|
174
|
+
const result = applyT0Auto(r.ruleId, r.output, cwd)
|
|
175
|
+
if (result.applied) {
|
|
176
|
+
applied.push({ ruleId: r.ruleId, actions: result.actions })
|
|
177
|
+
} else {
|
|
178
|
+
skipped.push(r.ruleId)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (applied.length === 0) {
|
|
183
|
+
console.log(`⏭️ fix-t0: T0-auto паттерн не підходить для: ${failed.map(r => r.ruleId).join(', ')}`)
|
|
184
|
+
return 1
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 3. Вивести що зробили
|
|
188
|
+
for (const { ruleId, actions } of applied) {
|
|
189
|
+
console.log(`⚙️ ${ruleId}:`)
|
|
190
|
+
for (const a of actions) console.log(` ${a}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 4. Check-gate: перевірити лише ті правила, що ми чіпали
|
|
194
|
+
const recheck = spawnSync(
|
|
195
|
+
'bun',
|
|
196
|
+
[N_CURSOR_BIN, '_fix-check', ...applied.map(a => a.ruleId)],
|
|
197
|
+
{ cwd, encoding: 'utf8', timeout: 120_000 }
|
|
198
|
+
)
|
|
199
|
+
const recheckRaw = recheck.stdout?.trim()
|
|
200
|
+
if (!recheckRaw) {
|
|
201
|
+
console.error(`fix-t0: check-gate: fix --json повернув порожній stdout`)
|
|
202
|
+
return 1
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const recheckJson = JSON.parse(recheckRaw)
|
|
206
|
+
const stillFailed = recheckJson.rules.filter(r => !r.ok)
|
|
207
|
+
|
|
208
|
+
if (verbose) {
|
|
209
|
+
for (const r of recheckJson.rules) {
|
|
210
|
+
console.log(` ${r.ok ? '✅' : '❌'} ${r.ruleId}`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (stillFailed.length > 0) {
|
|
215
|
+
console.log(`❌ fix-t0 check-gate: ${stillFailed.map(r => r.ruleId).join(', ')} — лишились`)
|
|
216
|
+
if (skipped.length > 0) {
|
|
217
|
+
console.log(`⏭️ без T0 паттерну: ${skipped.join(', ')} → потрібен LLM`)
|
|
218
|
+
}
|
|
219
|
+
return 1
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const totalFixed = applied.length
|
|
223
|
+
const total = failed.length
|
|
224
|
+
console.log(
|
|
225
|
+
`✅ fix-t0: ${totalFixed}/${total} правил закрито T0-auto` +
|
|
226
|
+
(skipped.length > 0 ? `; ${skipped.join(', ')} → T1 (LLM)` : '')
|
|
227
|
+
)
|
|
228
|
+
return 0
|
|
229
|
+
}
|
package/skills/fix/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": "завжди", "worktree": true }
|
|
1
|
+
{ "auto": "завжди", "worktree": true, "orchestrator": true }
|