@nitra/cursor 10.0.1 → 10.1.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 +6 -0
- package/package.json +1 -1
- package/rules/doc-files/js/docgen-judge-measure.mjs +147 -0
- package/scripts/lib/docs/run-rule-cli.md +6 -9
- package/scripts/lib/fix/docs/run-fix-check.md +6 -5
- package/scripts/lib/fix/run-fix-check.mjs +28 -7
- package/scripts/lib/run-rule-cli.mjs +7 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [10.1.0] - 2026-06-15
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Конформність-селекція: .n-cursor.json — єдине джерело правди. resolveCheckRuleIds бере активні правила прямо з конфіга (available ∩ enabled), .cursor/rules/*.mdc лишається лише fallback'ом коли конфіга нема. Прибрано дрейф «правило enabled, але .mdc нема → тихо пропущено». Per-rule whitelist-гейт у runRuleCli видалено як дубль — гейтинг живе виключно у селекції.
|
|
8
|
+
|
|
3
9
|
## [10.0.1] - 2026-06-14
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/package.json
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* docgen-judge-measure.mjs — Q4 офлайн-вимірювач (spec 2026-06-14-docgen-judge-design).
|
|
4
|
+
*
|
|
5
|
+
* Міряє false-positive rate детермінованого `scoreDoc`: серед доків, що ПРОЙШЛИ
|
|
6
|
+
* (score ≥ threshold), який % сильна хмарна модель-суддя класифікує як
|
|
7
|
+
* `generic`/`inaccurate`. Це число вирішує, чи будувати рантайм-judge-гейт.
|
|
8
|
+
*
|
|
9
|
+
* Генерація: локальна (N_LOCAL_MIN_MODEL, omlx/* → прямий HTTP) — реальний пайплайн.
|
|
10
|
+
* Суддя: openai-codex/gpt-5.4-mini (сильніша хмара, ніж генератор — інакше вимір беззмістовний).
|
|
11
|
+
* Обидва — через існуючий `../../../lib/llm.mjs callLlm` (маршрутизація за префіксом).
|
|
12
|
+
*
|
|
13
|
+
* Кеш на диску (за хешем контенту) → повторні прогони не регенерують і не пересуджують.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node docgen-judge-measure.mjs <file1> <file2> ...
|
|
17
|
+
* MEASURE_CACHE=/tmp/x N_CURSOR_DOCGEN_JUDGE_MODEL=openai-codex/gpt-5.4 node docgen-judge-measure.mjs ...
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
20
|
+
import { createHash } from 'node:crypto'
|
|
21
|
+
import { join } from 'node:path'
|
|
22
|
+
import { generateDoc } from './docgen-gen.mjs'
|
|
23
|
+
import { callLlm } from '../../../lib/llm.mjs'
|
|
24
|
+
import { QUALITY_THRESHOLD } from './docgen-crc.mjs'
|
|
25
|
+
|
|
26
|
+
const env = process.env
|
|
27
|
+
const GEN_MODEL = env.N_LOCAL_MIN_MODEL ?? 'omlx/gemma-4-e4b-it-OptiQ-4bit'
|
|
28
|
+
const JUDGE_MODEL = env.N_CURSOR_DOCGEN_JUDGE_MODEL ?? 'openai-codex/gpt-5.4-mini'
|
|
29
|
+
const THRESHOLD = Number(env.N_CURSOR_DOC_FILES_THRESHOLD ?? QUALITY_THRESHOLD) || 70
|
|
30
|
+
const CACHE_DIR = env.MEASURE_CACHE ?? '/tmp/docgen-judge-measure'
|
|
31
|
+
const JUDGE_TIMEOUT = Number(env.MEASURE_JUDGE_TIMEOUT_MS ?? 120_000)
|
|
32
|
+
|
|
33
|
+
const SYSTEM = `You are a strict technical-documentation reviewer. You receive a SOURCE file and an auto-generated Markdown DOC describing it. Classify the DOC into exactly one verdict:
|
|
34
|
+
- "accurate": specific to THIS file AND every factual claim is supported by the source.
|
|
35
|
+
- "generic": could describe almost any file of this kind; vague/boilerplate; lacks file-specific substance.
|
|
36
|
+
- "inaccurate": contains at least one claim that is NOT supported by, or is contradicted by, the source code.
|
|
37
|
+
Prefer "inaccurate" over "generic" if any claim is wrong. Respond with ONLY a JSON object, no prose:
|
|
38
|
+
{"verdict":"accurate|generic|inaccurate","confidence":0.0-1.0,"reason":"<20-300 chars>","offending":["<short quote from doc>"]}`
|
|
39
|
+
|
|
40
|
+
const sha = s => createHash('sha256').update(s).digest('hex').slice(0, 16)
|
|
41
|
+
|
|
42
|
+
function cacheGet(key) {
|
|
43
|
+
const p = join(CACHE_DIR, key + '.json')
|
|
44
|
+
return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : null
|
|
45
|
+
}
|
|
46
|
+
function cacheSet(key, val) {
|
|
47
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true })
|
|
48
|
+
writeFileSync(join(CACHE_DIR, key + '.json'), JSON.stringify(val))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Генерує (з кешем за хешем src). */
|
|
52
|
+
function genCached(file, src) {
|
|
53
|
+
const key = 'gen-' + sha(GEN_MODEL + '\0' + src)
|
|
54
|
+
const hit = cacheGet(key)
|
|
55
|
+
if (hit) return { ...hit, cached: true }
|
|
56
|
+
const r = generateDoc(file, { model: GEN_MODEL })
|
|
57
|
+
const out = { md: r.md, score: r.score, issues: r.issues, degraded: r.degraded }
|
|
58
|
+
cacheSet(key, out)
|
|
59
|
+
return { ...out, cached: false }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Судить (з кешем за хешем src+doc). */
|
|
63
|
+
function judgeCached(src, doc) {
|
|
64
|
+
const key = 'judge-' + sha(JUDGE_MODEL + '\0' + src + '\0' + doc)
|
|
65
|
+
const hit = cacheGet(key)
|
|
66
|
+
if (hit) return { ...hit, cached: true }
|
|
67
|
+
const user = `SOURCE FILE:\n\`\`\`\n${src.slice(0, 12000)}\n\`\`\`\n\nGENERATED DOC:\n\`\`\`md\n${doc.slice(0, 8000)}\n\`\`\`\n\nReturn the JSON verdict.`
|
|
68
|
+
const raw = callLlm([{ role: 'system', content: SYSTEM }, { role: 'user', content: user }], JUDGE_MODEL, { timeoutMs: JUDGE_TIMEOUT, temperature: 0 })
|
|
69
|
+
const a = raw.indexOf('{'), b = raw.lastIndexOf('}')
|
|
70
|
+
if (a < 0 || b < 0) throw new Error('no JSON in judge reply: ' + raw.slice(0, 160))
|
|
71
|
+
const v = JSON.parse(raw.slice(a, b + 1))
|
|
72
|
+
cacheSet(key, v)
|
|
73
|
+
return { ...v, cached: false }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function main() {
|
|
77
|
+
const files = process.argv.slice(2).filter(f => !f.startsWith('--'))
|
|
78
|
+
if (!files.length) {
|
|
79
|
+
console.error('Usage: node docgen-judge-measure.mjs <file1> <file2> ...')
|
|
80
|
+
process.exit(2)
|
|
81
|
+
}
|
|
82
|
+
console.error(`[measure] gen=${GEN_MODEL} judge=${JUDGE_MODEL} threshold=${THRESHOLD} files=${files.length} cache=${CACHE_DIR}`)
|
|
83
|
+
|
|
84
|
+
const rows = []
|
|
85
|
+
for (const [i, file] of files.entries()) {
|
|
86
|
+
const tag = `(${i + 1}/${files.length}) ${file}`
|
|
87
|
+
let src
|
|
88
|
+
try { src = readFileSync(file, 'utf8') } catch (e) { console.error(`[skip] ${tag}: read ${e.message}`); continue }
|
|
89
|
+
|
|
90
|
+
let gen
|
|
91
|
+
try { gen = genCached(file, src) } catch (e) { console.error(`[gen-err] ${tag}: ${e.message.slice(0, 120)}`); rows.push({ file, error: 'gen', detail: e.message.slice(0, 200) }); continue }
|
|
92
|
+
if (gen.score == null) { console.error(`[unsupported] ${tag}`); rows.push({ file, score: null, unsupported: true }); continue }
|
|
93
|
+
|
|
94
|
+
const passed = gen.score >= THRESHOLD
|
|
95
|
+
const row = { file, score: gen.score, degraded: gen.degraded, passed, genCached: gen.cached }
|
|
96
|
+
console.error(`[gen${gen.cached ? '*' : ''}] ${tag} score=${gen.score} ${passed ? 'PASS' : 'degraded'}`)
|
|
97
|
+
|
|
98
|
+
if (passed) {
|
|
99
|
+
try {
|
|
100
|
+
const v = judgeCached(src, gen.md)
|
|
101
|
+
row.verdict = v.verdict; row.confidence = v.confidence; row.reason = v.reason; row.offending = v.offending; row.judgeCached = v.cached
|
|
102
|
+
console.error(` [judge${v.cached ? '*' : ''}] ${v.verdict} (${v.confidence}) — ${(v.reason || '').slice(0, 90)}`)
|
|
103
|
+
} catch (e) { row.judgeError = e.message.slice(0, 200); console.error(` [judge-err] ${e.message.slice(0, 120)}`) }
|
|
104
|
+
}
|
|
105
|
+
rows.push(row)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Aggregate
|
|
109
|
+
const scored = rows.filter(r => typeof r.score === 'number')
|
|
110
|
+
const passedRows = scored.filter(r => r.passed && r.verdict)
|
|
111
|
+
const byVerdict = { accurate: 0, generic: 0, inaccurate: 0 }
|
|
112
|
+
for (const r of passedRows) byVerdict[r.verdict] = (byVerdict[r.verdict] ?? 0) + 1
|
|
113
|
+
const M = passedRows.length
|
|
114
|
+
const bad = byVerdict.generic + byVerdict.inaccurate
|
|
115
|
+
const pct = n => (M ? ((100 * n) / M).toFixed(1) : '—')
|
|
116
|
+
|
|
117
|
+
const report = {
|
|
118
|
+
config: { genModel: GEN_MODEL, judgeModel: JUDGE_MODEL, threshold: THRESHOLD },
|
|
119
|
+
counts: {
|
|
120
|
+
files: files.length, generated: scored.length,
|
|
121
|
+
unsupported: rows.filter(r => r.unsupported).length,
|
|
122
|
+
genErrors: rows.filter(r => r.error === 'gen').length,
|
|
123
|
+
passedDetScorer: scored.filter(r => r.passed).length,
|
|
124
|
+
judged: M, judgeErrors: rows.filter(r => r.judgeError).length
|
|
125
|
+
},
|
|
126
|
+
falsePositiveRate: { // серед PASSED+judged
|
|
127
|
+
accurate: byVerdict.accurate, generic: byVerdict.generic, inaccurate: byVerdict.inaccurate,
|
|
128
|
+
badPct: pct(bad), inaccuratePct: pct(byVerdict.inaccurate), genericPct: pct(byVerdict.generic)
|
|
129
|
+
},
|
|
130
|
+
offenders: passedRows.filter(r => r.verdict !== 'accurate').map(r => ({ file: r.file, score: r.score, verdict: r.verdict, confidence: r.confidence, reason: r.reason })),
|
|
131
|
+
rows
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true })
|
|
135
|
+
const out = join(CACHE_DIR, 'report.json')
|
|
136
|
+
writeFileSync(out, JSON.stringify(report, null, 2))
|
|
137
|
+
|
|
138
|
+
console.log('\n===== Q4 MEASUREMENT =====')
|
|
139
|
+
console.log(`generated: ${report.counts.generated}/${files.length} (unsupported=${report.counts.unsupported}, gen-errors=${report.counts.genErrors})`)
|
|
140
|
+
console.log(`passed det-scorer (score≥${THRESHOLD}): ${report.counts.passedDetScorer} judged: ${M}`)
|
|
141
|
+
console.log(`among PASSED+judged → accurate=${byVerdict.accurate} generic=${byVerdict.generic} inaccurate=${byVerdict.inaccurate}`)
|
|
142
|
+
console.log(`>>> det-scorer FALSE-POSITIVE rate: ${pct(bad)}% (inaccurate=${pct(byVerdict.inaccurate)}%, generic=${pct(byVerdict.generic)}%)`)
|
|
143
|
+
console.log(`decision guide: <~5% → don't build gate; >~15% → build (inaccurate-only)`)
|
|
144
|
+
console.log(`report: ${out}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/scripts/lib/run-rule-cli.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: 264e7ab0
|
|
5
5
|
score: 100
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,18 +9,15 @@ docgen:
|
|
|
9
9
|
|
|
10
10
|
## Огляд
|
|
11
11
|
|
|
12
|
-
Файл є автономним CLI-запускачем для одного правила. Він
|
|
12
|
+
Файл є автономним CLI-запускачем для одного правила. Він друкує звіт про перевірку та повертає агрегований код виходу. Whitelist-гейту тут немає: гейтинг активних правил живе виключно у `resolveCheckRuleIds` (селекція за `.n-cursor.json`), а прямий запуск файлу правила — свідома debug/override-дія, тож виконується беззастережно.
|
|
13
13
|
|
|
14
14
|
## Поведінка
|
|
15
15
|
|
|
16
16
|
1. Викликається для запуску правила.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
20
|
-
5.
|
|
21
|
-
6. Використовує кеш для перевірки.
|
|
22
|
-
7. Викликає функцію для виконання стандартного правила з кешем.
|
|
23
|
-
8. Повертає агрегований код виходу.
|
|
17
|
+
2. Друкує повідомлення про перевірку правила.
|
|
18
|
+
3. Використовує кеш для перевірки.
|
|
19
|
+
4. Викликає функцію для виконання стандартного правила з кешем.
|
|
20
|
+
5. Повертає агрегований код виходу.
|
|
24
21
|
|
|
25
22
|
## Гарантії поведінки
|
|
26
23
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/scripts/lib/fix/run-fix-check.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: 76874730
|
|
5
5
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
6
6
|
score: 100
|
|
7
7
|
---
|
|
@@ -10,15 +10,16 @@ docgen:
|
|
|
10
10
|
|
|
11
11
|
## Огляд
|
|
12
12
|
|
|
13
|
-
Викликає конформність-фазу `lint` (read-only), движок (`orchestrator.mjs`, `t0.mjs`) та PostToolUse-хук. Перевірка конформності виконується як пряма функція, без зовнішньої обгортки через `subprocess`. Ізоляція на рівні кожного правила зберігається: кожен файл `rules/<id>/fix.mjs` все ще запускається окремим процесом `bun` (
|
|
13
|
+
Викликає конформність-фазу `lint` (read-only), движок (`orchestrator.mjs`, `t0.mjs`) та PostToolUse-хук. Перевірка конформності виконується як пряма функція, без зовнішньої обгортки через `subprocess`. Ізоляція на рівні кожного правила зберігається: кожен файл `rules/<id>/fix.mjs` все ще запускається окремим процесом `bun` (crash-isolation). Селекція активних правил — єдине джерело: `resolveCheckRuleIds` за `.n-cursor.json`; per-rule whitelist у спавнених процесах прибрано як дубль.
|
|
14
14
|
|
|
15
15
|
## Поведінка
|
|
16
16
|
|
|
17
17
|
1. Визначається наявність інструменту `conftest`.
|
|
18
18
|
2. Отримується список усіх доступних ідентифікаторів правил з каталогу правил.
|
|
19
|
-
3. Визначається список ідентифікаторів правил для
|
|
20
|
-
а. Якщо надано явний список
|
|
21
|
-
б. Якщо
|
|
19
|
+
3. Визначається список ідентифікаторів правил для прогону (`resolveCheckRuleIds`), де `.n-cursor.json` — єдине джерело правди:
|
|
20
|
+
а. Якщо надано явний список правил — він валідується проти доступних і звужується до активних (вимкнене правило не вмикається навіть на явний запит).
|
|
21
|
+
б. Якщо явного списку нема й конфіг є — беруться активні правила конфіга (`available ∩ enabled`); `.cursor/rules/*.mdc` ігнорується (фікс дрейфу «enabled, але .mdc нема»).
|
|
22
|
+
в. Якщо конфіга нема (open-by-default debug) — fallback на скан `.cursor/rules/*.mdc`.
|
|
22
23
|
4. Якщо визначено ідентифікатори правил для прогону, для кожного ідентифікатора запускається окремий процес `bun` з файлом `fix.mjs` відповідного правила.
|
|
23
24
|
5. Захоплюється вивід кожного процесу.
|
|
24
25
|
6. Підраховується загальна кількість правил, що не пройшли перевірку.
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* (`orchestrator.mjs`, `t0.mjs`) і PostToolUse-хук.
|
|
5
5
|
*
|
|
6
6
|
* Per-rule ізоляція зберігається: кожне `rules/<id>/fix.mjs` усе ще запускається окремим
|
|
7
|
-
* процесом `bun` (
|
|
8
|
-
*
|
|
7
|
+
* процесом `bun` (crash-isolation). Прибрано лише зовнішній wrapper-subprocess, що його
|
|
8
|
+
* раніше шелили оркестратор/хук.
|
|
9
|
+
*
|
|
10
|
+
* Селекція активних правил — виключно тут (`resolveCheckRuleIds` за `.n-cursor.json`);
|
|
11
|
+
* per-rule whitelist у спавнених процесах прибрано як дубль (див. `runRuleCli`).
|
|
9
12
|
*/
|
|
10
13
|
import { spawnSync } from 'node:child_process'
|
|
11
14
|
import { dirname, join } from 'node:path'
|
|
@@ -16,24 +19,42 @@ import { listRuleIds } from '../list-rule-ids.mjs'
|
|
|
16
19
|
import { ensureTool } from '../ensure-tool.mjs'
|
|
17
20
|
import { discoverCheckRulesFromCursorRules } from '../discover-check-rules-from-cursor.mjs'
|
|
18
21
|
import { listProjectRulesMdcFiles } from '../list-project-rules-mdc.mjs'
|
|
22
|
+
import { isRuleEnabled, readNCursorConfigLite } from '../read-n-cursor-config-lite.mjs'
|
|
19
23
|
|
|
20
24
|
// Цей файл: npm/scripts/lib/fix/run-fix-check.mjs → npm/rules (чотири dirname угору + rules).
|
|
21
25
|
const BUNDLED_RULES_DIR = join(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))), 'rules')
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
* Визначає id правил для
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* Визначає id правил для прогону. `.n-cursor.json` — **єдине джерело правди** селекції:
|
|
29
|
+
* - явні `requestedRules` — валідуються проти `available`, тоді звужуються до активних
|
|
30
|
+
* (явний запит лише фільтрує всередині активних, не вмикає вимкнене правило);
|
|
31
|
+
* - без явних і конфіг **є** — беремо саме активні правила конфіга (`available ∩ enabled`).
|
|
32
|
+
* Це прибирає дрейф «правило в `.n-cursor.json:rules`, але `.cursor/rules/*.mdc` нема
|
|
33
|
+
* (sync не прогнаний) → раніше тихо пропускалось»;
|
|
34
|
+
* - без явних і конфіга **нема** — open-by-default debug: fallback на зматеріалізовані
|
|
35
|
+
* `.cursor/rules/*.mdc` (єдиний сигнал «що встановлено», коли немає whitelist).
|
|
36
|
+
*
|
|
37
|
+
* Per-rule дубль-гейту (`runRuleCli → isRuleEnabled`) більше немає — гейтинг живе лише тут.
|
|
38
|
+
* @param {string[]} requestedRules запитані (порожній → auto-селекція)
|
|
39
|
+
* @param {string[]} available доступні rule-id у пакеті (алфавітно)
|
|
27
40
|
* @param {string} cwd корінь
|
|
28
41
|
* @returns {Promise<string[]>} id для прогону (можливо порожній)
|
|
29
42
|
* @throws {Error} на невідомих явно заданих правилах
|
|
30
43
|
*/
|
|
31
|
-
async function resolveCheckRuleIds(requestedRules, available, cwd) {
|
|
44
|
+
export async function resolveCheckRuleIds(requestedRules, available, cwd) {
|
|
45
|
+
const config = await readNCursorConfigLite(cwd)
|
|
46
|
+
|
|
32
47
|
if (requestedRules.length > 0) {
|
|
33
48
|
const unknown = requestedRules.filter(id => !available.includes(id))
|
|
34
49
|
if (unknown.length > 0) throw new Error(`Unknown rules: ${unknown.join(', ')}`)
|
|
35
|
-
return requestedRules
|
|
50
|
+
return requestedRules.filter(id => isRuleEnabled(config, id))
|
|
36
51
|
}
|
|
52
|
+
|
|
53
|
+
if (config.exists) {
|
|
54
|
+
return available.filter(id => isRuleEnabled(config, id))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Конфіга нема → fallback на зматеріалізоване (debug / open-by-default).
|
|
37
58
|
const mdcFiles = await listProjectRulesMdcFiles(cwd)
|
|
38
59
|
if (mdcFiles.length === 0) return []
|
|
39
60
|
return discoverCheckRulesFromCursorRules(available, mdcFiles)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standalone CLI runner для одного правила. Викликається з `rules/<id>/fix.mjs`
|
|
3
3
|
* у блоці `if (import.meta.main)` — це робить `bun rules/<id>/fix.mjs` повним
|
|
4
|
-
* еквівалентом
|
|
5
|
-
*
|
|
4
|
+
* еквівалентом `npx \@nitra/cursor fix <id>`: друкує summary, повертає aggregated exit-code.
|
|
5
|
+
*
|
|
6
|
+
* **Без whitelist-гейту.** Гейтинг активних правил — єдине джерело: `resolveCheckRuleIds`
|
|
7
|
+
* (`scripts/lib/fix/run-fix-check.mjs`) за `.n-cursor.json`. Прямий `bun rules/<id>/fix.mjs` —
|
|
8
|
+
* свідомий запуск саме цього правила (debug / override), тож виконується беззастережно;
|
|
9
|
+
* усі автоматичні шляхи (lint-конформність, orchestrator, t0, hook) уже спавнять лише активні.
|
|
6
10
|
*
|
|
7
11
|
* Library-mode виклик з CLI orchestration — інше: див. `runStandardRule` + `fix.mjs::run(ctx)`.
|
|
8
12
|
*/
|
|
9
13
|
import { basename } from 'node:path'
|
|
10
14
|
|
|
11
|
-
import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
|
|
12
15
|
import { runStandardRule } from './run-standard-rule.mjs'
|
|
13
16
|
import { getOrCreateWalkCache } from '../utils/walk-cache.mjs'
|
|
14
17
|
|
|
@@ -21,16 +24,10 @@ const PACKAGE_NAME = '@nitra/cursor'
|
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* @param {string} ruleDir абсолютний шлях до `rules/<id>/`
|
|
24
|
-
* @returns {Promise<number>} 0 — OK
|
|
27
|
+
* @returns {Promise<number>} 0 — OK; 1 — порушення
|
|
25
28
|
*/
|
|
26
29
|
export async function runRuleCli(ruleDir) {
|
|
27
30
|
const ruleId = basename(ruleDir)
|
|
28
|
-
const config = await readNCursorConfigLite()
|
|
29
|
-
|
|
30
|
-
if (!isRuleEnabled(config, ruleId)) {
|
|
31
|
-
console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — правило не в \`.n-cursor.json:rules\`. Пропущено.\n`)
|
|
32
|
-
return 0
|
|
33
|
-
}
|
|
34
31
|
|
|
35
32
|
console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — перевірка правила\n`)
|
|
36
33
|
|