Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@nitra/cursor

Package Overview
Dependencies
Maintainers
1
Versions
406
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nitra/cursor - npm Package Compare versions

Comparing version
9.4.0
to
10.0.0
+76
scripts/lib/fix/run-fix-check.mjs
/**
* Конформність-детект (колишній subcommand `_fix-check`) як ПРЯМА функція — без subprocess-обгортки
* `bun n-cursor.js _fix-check`. Викликають конформність-фаза `lint` (read-only), движок
* (`orchestrator.mjs`, `t0.mjs`) і PostToolUse-хук.
*
* Per-rule ізоляція зберігається: кожне `rules/<id>/fix.mjs` усе ще запускається окремим
* процесом `bun` (config-loading + whitelist + crash-isolation). Прибрано лише зовнішній
* wrapper-subprocess, що його раніше шелили оркестратор/хук.
*/
import { spawnSync } from 'node:child_process'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { cwd as processCwd } from 'node:process'
import { listRuleIds } from '../list-rule-ids.mjs'
import { ensureTool } from '../ensure-tool.mjs'
import { discoverCheckRulesFromCursorRules } from '../discover-check-rules-from-cursor.mjs'
import { listProjectRulesMdcFiles } from '../list-project-rules-mdc.mjs'
// Цей файл: npm/scripts/lib/fix/run-fix-check.mjs → npm/rules (чотири dirname угору + rules).
const BUNDLED_RULES_DIR = join(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))), 'rules')
/**
* Визначає id правил для прогону: явні (з валідацією) або discovery з `.cursor/rules/*.mdc`.
* @param {string[]} requestedRules запитані (порожній → discovery)
* @param {string[]} available доступні rule-id у пакеті
* @param {string} cwd корінь
* @returns {Promise<string[]>} id для прогону (можливо порожній)
* @throws {Error} на невідомих явно заданих правилах
*/
async function resolveCheckRuleIds(requestedRules, available, cwd) {
if (requestedRules.length > 0) {
const unknown = requestedRules.filter(id => !available.includes(id))
if (unknown.length > 0) throw new Error(`Unknown rules: ${unknown.join(', ')}`)
return requestedRules
}
const mdcFiles = await listProjectRulesMdcFiles(cwd)
if (mdcFiles.length === 0) return []
return discoverCheckRulesFromCursorRules(available, mdcFiles)
}
/**
* Прогоняє `fix.mjs` кожного правила окремим процесом, захоплюючи output.
* @param {string[]} idsToRun правила
* @param {string} cwd корінь
* @returns {{ totalFailed:number, rules:Array<{ruleId:string, ok:boolean, output:string}> }} результат
*/
function runRuleFixProcesses(idsToRun, cwd) {
let totalFailed = 0
const rules = []
for (const id of idsToRun) {
const r = spawnSync('bun', [join(BUNDLED_RULES_DIR, id, 'fix.mjs')], { cwd, encoding: 'utf8' })
const ok = r.status === 0
rules.push({ ruleId: id, ok, output: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() })
if (!ok) totalFailed++
}
return { totalFailed, rules }
}
/**
* Конформність-детект: per-rule `fix.mjs run()` (= перевірка, без мутацій).
* @param {string[]} [requestedRules] фільтр (порожній → discovery з `.cursor/rules/`)
* @param {string} [cwd] корінь
* @returns {Promise<{ total:number, failed:number, rules:Array<{ruleId:string, ok:boolean, output:string}> }>} результат
*/
export async function runFixCheck(requestedRules = [], cwd = processCwd()) {
ensureTool('conftest')
const available = await listRuleIds(BUNDLED_RULES_DIR)
if (available.length === 0) return { total: 0, failed: 0, rules: [] }
const idsToRun = await resolveCheckRuleIds(requestedRules, available, cwd)
if (idsToRun.length === 0) return { total: 0, failed: 0, rules: [] }
const { totalFailed, rules } = runRuleFixProcesses(idsToRun, cwd)
return { total: idsToRun.length, failed: totalFailed, rules }
}
/**
* Список `.mdc`-файлів правил у `.cursor/rules/` проєкту-споживача (відсортований).
* Винесено зі `bin/n-cursor.js`, щоб ділити між CLI-dispatch і `run-fix-check` (конформність-детект).
*/
import { existsSync } from 'node:fs'
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
import { cwd as processCwd } from 'node:process'
/** Каталог правил у проєкті-споживачі (відносно кореня). */
export const CURSOR_RULES_DIR = '.cursor/rules'
/**
* @param {string} [cwd] корінь проєкту
* @returns {Promise<string[]>} імена `*.mdc` (відсортовані), або `[]` якщо каталогу немає
*/
export async function listProjectRulesMdcFiles(cwd = processCwd()) {
const dir = join(cwd, CURSOR_RULES_DIR)
if (!existsSync(dir)) return []
const names = await readdir(dir)
return names.filter(n => n.endsWith('.mdc')).toSorted((a, b) => a.localeCompare(b))
}
+1
-1
{
"name": "@nitra/cursor",
"version": "9.4.0",
"version": "10.0.0",
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",

@@ -5,0 +5,0 @@ "keywords": [

@@ -17,3 +17,2 @@ /**

import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
import { cwd as processCwd } from 'node:process'

@@ -27,3 +26,2 @@

const RULES_DIR = join(PACKAGE_ROOT, 'rules')
const N_CURSOR_BIN = join(PACKAGE_ROOT, 'bin', 'n-cursor.js')

@@ -46,16 +44,7 @@ /**

}
const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...filter], { cwd, encoding: 'utf8', timeout: 600_000 })
let parsed = null
try {
parsed = JSON.parse((r.stdout ?? '').trim())
} catch {
parsed = null
}
if (!parsed) {
log('❌ lint: конформність — помилка перевірки (_fix-check не повернув JSON)\n')
return 1
}
const failed = parsed.rules.filter(/** @param {{ok:boolean}} x */ x => !x.ok)
const { runFixCheck } = await import('../../../scripts/lib/fix/run-fix-check.mjs')
const { rules } = await runFixCheck(filter, cwd)
const failed = rules.filter(x => !x.ok)
if (failed.length === 0) return 0
log(`❌ lint: конформність — ${failed.length} порушень: ${failed.map(/** @param {{ruleId:string}} x */ x => x.ruleId).join(', ')}\n`)
log(`❌ lint: конформність — ${failed.length} порушень: ${failed.map(x => x.ruleId).join(', ')}\n`)
for (const f of failed) if (f.output) log(`${f.output}\n`)

@@ -62,0 +51,0 @@ return 1

/** @see ./docs/orchestrator.md */
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { runFixCheck } from './run-fix-check.mjs'
import { runT0AutoCli } from './t0.mjs'
const HERE = fileURLToPath(new URL('.', import.meta.url))
const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
const DEFAULT_MAX_ITER = 3

@@ -32,9 +28,9 @@ const ESCALATE_AFTER = 2

* @param {Array<{ ruleId: string }>} failed правила перед кроком
* @returns {Array<{ ruleId: string, ok: boolean, output: string }>} правила після T0
* @returns {Promise<Array<{ ruleId: string, ok: boolean, output: string }>>} правила після T0
*/
function runT0Step(cwd, ruleFilter, failed) {
spawnSync('bun', [N_CURSOR_BIN, 'fix-t0', ...ruleFilter], { cwd, stdio: 'pipe' })
async function runT0Step(cwd, ruleFilter, failed) {
await runT0AutoCli([...ruleFilter], cwd)
const afterT0 = runFixCheck(cwd, ruleFilter)
const failedAfterT0 = afterT0?.rules.filter(r => !r.ok) ?? failed
const afterT0 = await runFixCheck(ruleFilter, cwd)
const failedAfterT0 = afterT0.rules.filter(r => !r.ok)
const t0Fixed = failed.filter(r => !failedAfterT0.some(f => f.ruleId === r.ruleId))

@@ -88,8 +84,3 @@

// ── Перша перевірка (тихо) ──
const initial = runFixCheck(cwd, ruleFilter)
if (!initial) {
console.error(`❌ fix: помилка перевірки`)
return 1
}
const initial = await runFixCheck(ruleFilter, cwd)
let failed = initial.rules.filter(r => !r.ok)

@@ -109,3 +100,3 @@ const total = initial.total

for (let iter = 1; iter <= maxIter; iter++) {
failed = runT0Step(cwd, ruleFilter, failed)
failed = await runT0Step(cwd, ruleFilter, failed)
if (failed.length === 0) break

@@ -116,4 +107,4 @@

// Перевірка після LLM
const afterLLM = runFixCheck(cwd, ruleFilter)
failed = afterLLM?.rules.filter(r => !r.ok) ?? failed
const afterLLM = await runFixCheck(ruleFilter, cwd)
failed = afterLLM.rules.filter(r => !r.ok)
if (failed.length === 0) break

@@ -130,23 +121,1 @@ }

}
/**
* Внутрішня check-gate: запускає fix-перевірки і повертає структурований результат.
* Не є публічним CLI — викликається лише оркестратором.
* @param {string} cwd корінь проєкту
* @param {string[]} ruleFilter список ID правил (порожній — усі)
* @returns {{ total: number, failed: number, rules: Array<{ ruleId: string, ok: boolean, output: string }> } | null} JSON-результат або null якщо stdout порожній/невалідний
*/
function runFixCheck(cwd, ruleFilter = []) {
const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], {
cwd,
encoding: 'utf8',
timeout: 120_000
})
const stdout = r.stdout?.trim()
if (!stdout) return null
try {
return JSON.parse(stdout)
} catch {
return null
}
}
/** @see ./docs/t0.md */
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { runFixCheck } from './run-fix-check.mjs'
const REC_REQUIRE_RE = /recommendations має містити "[^"]+"/

@@ -112,24 +112,3 @@ const REC_MATCH_ALL_RE = /recommendations має містити "([^"]+)"/g

const HERE = dirname(fileURLToPath(import.meta.url))
/** Абсолютний шлях до npm/bin/n-cursor.js відносно цього файлу */
const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
/**
* Запускає `_fix-check` і парсить JSON-результат.
* @param {string[]} ruleFilter список rule-ids (порожній — усі)
* @param {string} cwd корінь проєкту
* @returns {{ rules: Array<{ ruleId: string, ok: boolean, output: string }> } | { _empty: true } | { _badJson: true }} JSON або маркер помилки
*/
function fixCheck(ruleFilter, cwd) {
const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], { cwd, encoding: 'utf8', timeout: 120_000 })
const raw = r.stdout?.trim()
if (!raw) return { _empty: true, stderr: r.stderr }
try {
return JSON.parse(raw)
} catch {
return { _badJson: true }
}
}
/**
* Застосовує T0-auto до кожного провального правила, розділяючи на applied/skipped.

@@ -158,22 +137,12 @@ * @param {Array<{ ruleId: string, output: string }>} failed провальні правила

* повторно перевіряє check-gate, виводить підсумок.
* @param {string[]} args аргументи підкоманди (опційний список rule-ids)
* @param {string[]} args аргументи (опційний список rule-ids)
* @param {string} cwd корінь проєкту
* @returns {Promise<number>} 0 — T0-auto закрив всі або немає порушень; 1 — лишились
*/
export function runT0AutoCli(args, cwd) {
export async function runT0AutoCli(args, cwd) {
const ruleFilter = args.filter(a => !a.startsWith('--'))
const verbose = args.includes('--verbose') || args.includes('-v')
// 1. Запустити fix --json
const fixJson = fixCheck(ruleFilter, cwd)
if (fixJson._empty) {
console.error(`n-cursor fix-t0: fix --json повернув порожній stdout`)
console.error(fixJson.stderr?.slice(0, 300) ?? '')
return 1
}
if (fixJson._badJson) {
console.error(`n-cursor fix-t0: fix --json повернув невалідний JSON`)
return 1
}
// 1. Конформність-детект (пряма функція, без subprocess)
const fixJson = await runFixCheck(ruleFilter, cwd)
const failed = fixJson.rules.filter(r => !r.ok)

@@ -200,7 +169,3 @@ if (failed.length === 0) {

// 4. Check-gate: перевірити лише ті правила, що ми чіпали
const recheckJson = fixCheck(applied.map(a => a.ruleId), cwd)
if (recheckJson._empty) {
console.error(`fix-t0: check-gate: fix --json повернув порожній stdout`)
return 1
}
const recheckJson = await runFixCheck(applied.map(a => a.ruleId), cwd)
const stillFailed = recheckJson.rules.filter(r => !r.ok)

@@ -207,0 +172,0 @@

@@ -11,8 +11,10 @@ /**

* - stdin Claude Code: JSON із `tool_input.file_path`; якщо файлу немає (напр. Bash) — exit 0 (skip);
* - інакше spawn `_fix-check` (детект усіх правил), exit-код прозоро пробрасуємо (PostToolUse
* не блокує turn, але код лишаємо інформативним: 1 — є порушення конформності).
* - інакше пряма `runFixCheck` (детект усіх правил, без subprocess-обгортки), exit-код прозоро:
* 1 — є порушення конформності (PostToolUse не блокує turn, але код лишаємо інформативним).
*/
import { spawn } from 'node:child_process'
import { once } from 'node:events'
import { cwd as processCwd } from 'node:process'
import { runFixCheck } from './lib/fix/run-fix-check.mjs'
/**

@@ -61,5 +63,5 @@ * Зчитує stdin до EOF як utf8 рядок. На TTY — повертає `''` одразу.

* Параметри доступні для інʼєкції для тестів: `stdinJson` обходить read від `process.stdin`,
* `spawnFn` — заміна `node:child_process.spawn`.
* @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів
* @returns {Promise<number>} exit code (0 — пропущено / конформність ОК; інше — є порушення)
* `runFixCheckFn` — заміна `runFixCheck`.
* @param {{ stdinJson?: string, runFixCheckFn?: typeof runFixCheck }} [options] параметри для тестів
* @returns {Promise<number>} exit code (0 — пропущено / конформність ОК; 1 — є порушення)
*/

@@ -73,8 +75,11 @@ export async function runPostToolUseFixCli(options = {}) {

}
const spawnFn = options.spawnFn ?? spawn
// Один read-only виклик: детект конформності всіх активованих правил, без роутингу.
const child = spawnFn('npx', ['--no', '@nitra/cursor', '_fix-check'], { stdio: 'inherit' })
const check = options.runFixCheckFn ?? runFixCheck
// Один read-only детект конформності всіх активованих правил (пряма функція, без subprocess).
try {
const [code] = await once(child, 'exit')
return code ?? 1
const { failed, rules } = await check([], processCwd())
if (failed === 0) return 0
for (const r of rules.filter(x => !x.ok)) {
if (r.output) process.stderr.write(`${r.output}\n`)
}
return 1
} catch (error) {

@@ -81,0 +86,0 @@ process.stderr.write(`post-tool-use-fix: не вдалося запустити детект конформності — ${error.message}\n`)

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display