@nitra/cursor 11.2.0 → 11.3.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [11.3.0] - 2026-06-15
4
+
5
+ ### Changed
6
+
7
+ - lint: opt-in `meta.json: llmFix:true` тепер реально дротується (раніше прапор був декоративний — opportunistic LLM-fix біг просто на `!readOnly`). `runLint` читає `llmFix` з meta правила й передає в `lint(files, cwd, { readOnly, llmFix })`; правило без прапора лишається detect-only. Це й забезпечує safety-тріаж зі спеки (логічні лінтери не вмикають LLM-fix випадково). doc-files і text позначені `llmFix:true`; cspell-класифікація гейтиться через `llmFix` (проведено `runLintTextCli`/`runLintTextSteps`/`runCspellText`), standalone `lint-text` передає `llmFix:true`. Принагідно: justified `no-unsanitized/method`-disable на package-internal динамічний import у `runLint` (pre-existing).
8
+
3
9
  ## [11.2.0] - 2026-06-15
4
10
 
5
11
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -1568,7 +1568,8 @@ try {
1568
1568
  case 'lint-text': {
1569
1569
  // Канонічний lint-text: cspell → shellcheck → dotenv → markdownlint → v8r (text.mdc).
1570
1570
  // `--read-only` (CI): без авто-фіксу (markdownlint/shellcheck/dotenv) — нуль мутацій.
1571
- process.exitCode = await runLintTextCli({ readOnly: args.includes('--read-only') })
1571
+ // `llmFix:true` text llmFix-capable, тож standalone lint-text робить omlx-класифікацію cspell.
1572
+ process.exitCode = await runLintTextCli({ readOnly: args.includes('--read-only'), llmFix: true })
1572
1573
 
1573
1574
  break
1574
1575
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "11.2.0",
3
+ "version": "11.3.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -98,13 +98,14 @@ function collectStale(files, cwd) {
98
98
  * Крок агрегатора lint для doc-files (opportunistic LLM-fix tier).
99
99
  * @param {string[] | undefined} files quick: лише ці файли; undefined: весь репозиторій
100
100
  * @param {string} [cwd] корінь репо
101
- * @param {{ readOnly?: boolean }} [opts] readOnly: лише детект (CI/hook), без мутацій/LLM
101
+ * @param {{ readOnly?: boolean, llmFix?: boolean }} [opts] readOnly: лише детект (CI/hook);
102
+ * llmFix: opt-in opportunistic-генерація (з `meta.json: llmFix:true`) — без нього detect-only
102
103
  * @returns {Promise<number>} 0 — доки свіжі; 1 — є застарілі (детект, fix пропущено чи помилка генерації)
103
104
  */
104
- export async function lint(files, cwd = process.cwd(), { readOnly = false } = {}) {
105
+ export async function lint(files, cwd = process.cwd(), { readOnly = false, llmFix = false } = {}) {
105
106
  const stale = collectStale(files, cwd)
106
107
  if (stale.length === 0) return 0
107
- if (readOnly) return reportStale(stale)
108
+ if (readOnly || !llmFix) return reportStale(stale)
108
109
 
109
110
  // fix-by-default: opportunistic-генерація через спільне ядро (preflight omlx →
110
111
  // батч із circuit-breaker'ом). omlx недоступний → runGenerationBatch друкує причину
@@ -108,15 +108,23 @@ export async function runLint(opts = {}) {
108
108
  return 0
109
109
  }
110
110
 
111
- const ids = selectLintRules(readAllMeta(rulesDir), full)
111
+ const metaById = readAllMeta(rulesDir)
112
+ const ids = selectLintRules(metaById, full)
112
113
  for (const id of ids) {
113
114
  const lintPath = join(rulesDir, id, 'js', 'lint.mjs')
114
115
  if (!existsSync(lintPath)) {
115
116
  log(`⚠️ lint: правило ${id} має lint-фазу, але немає js/lint.mjs — пропускаю.\n`)
116
117
  continue
117
118
  }
119
+ // lintPath = join(rulesDir, id, …) — суто package-internal (rulesDir пакета + id зі
120
+ // selectLintRules за власним meta), не зовнішній вхід → ін'єкції немає.
121
+ // eslint-disable-next-line no-unsanitized/method
118
122
  const mod = await import(lintPath)
119
- const code = await mod.lint(changed, cwd, { readOnly })
123
+ // `llmFix` (opt-in opportunistic LLM-fix, спека 2026-06-15): лише правила з
124
+ // `meta.json: llmFix:true` отримують fix-сходинку; решта — detect-only. Це й
125
+ // забезпечує safety-тріаж (логічні лінтери не вмикають LLM-fix випадково).
126
+ const llmFix = metaById[id]?.llmFix === true
127
+ const code = await mod.lint(changed, cwd, { readOnly, llmFix })
120
128
  if (code !== 0) return code
121
129
  }
122
130
 
@@ -6,9 +6,10 @@ import { runLintTextCli } from '../lint/lint.mjs'
6
6
  /**
7
7
  * @param {string[] | undefined} _files ігнорується (whole-repo аналіз)
8
8
  * @param {string} [_cwd] корінь (ігнорується — CLI працює від process.cwd())
9
- * @param {{ readOnly?: boolean }} [opts] readOnly → детект без авто-фіксу (нуль мутацій)
9
+ * @param {{ readOnly?: boolean, llmFix?: boolean }} [opts] readOnly → детект без авто-фіксу (нуль мутацій);
10
+ * llmFix → opt-in omlx-класифікація cspell (з `meta.json: llmFix:true`)
10
11
  * @returns {Promise<number>} exit code
11
12
  */
12
13
  export function lint(_files, _cwd, opts = {}) {
13
- return runLintTextCli({ readOnly: opts.readOnly === true })
14
+ return runLintTextCli({ readOnly: opts.readOnly === true, llmFix: opts.llmFix === true })
14
15
  }
@@ -113,9 +113,10 @@ export function appendWordsToDict(cwd, words) {
113
113
  * cspell-крок lint-text: класифікація → словник (нова схема).
114
114
  * @param {string} [cwd] корінь
115
115
  * @param {boolean} [readOnly] true → лише детект (нуль мутацій)
116
+ * @param {boolean} [llmFix] opt-in omlx-класифікація (з `meta.json: llmFix:true`); без нього — лише детект
116
117
  * @returns {number} 0 — чисто; 1 — лишились знахідки / помилка середовища
117
118
  */
118
- export function runCspellText(cwd = process.cwd(), readOnly = false) {
119
+ export function runCspellText(cwd = process.cwd(), readOnly = false, llmFix = false) {
119
120
  const bin = resolveCmd('npx')
120
121
  if (!bin) {
121
122
  process.stderr.write('❌ npx не знайдено в PATH (cspell).\n')
@@ -124,7 +125,7 @@ export function runCspellText(cwd = process.cwd(), readOnly = false) {
124
125
 
125
126
  const first = detectCspell(cwd, bin)
126
127
  if (first.code === 0) return 0
127
- if (readOnly) {
128
+ if (readOnly || !llmFix) {
128
129
  process.stdout.write(first.out)
129
130
  return first.code
130
131
  }
@@ -97,9 +97,10 @@ function preflight(dep) {
97
97
  /**
98
98
  * Внутрішні кроки `lint-text` без локу.
99
99
  * @param {boolean} [readOnly] true → лише детект без авто-фіксу (нуль мутацій — CI/pre-commit)
100
+ * @param {boolean} [llmFix] opt-in omlx-класифікація cspell (інші кроки фіксяться детерміновано за readOnly)
100
101
  * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
101
102
  */
102
- function runLintTextSteps(readOnly = false) {
103
+ function runLintTextSteps(readOnly = false, llmFix = false) {
103
104
  // Auto-install: throws on failure → propagates as exit 1 from runStandardLint
104
105
  ensureTool('shellcheck')
105
106
  ensureTool('dotenv-linter')
@@ -107,8 +108,8 @@ function runLintTextSteps(readOnly = false) {
107
108
  // patch потрібен лише для авто-фіксу shellcheck; у read-only пропускаємо preflight.
108
109
  if (!readOnly && !preflight(PATCH_PREFLIGHT)) return 1
109
110
 
110
- console.log(`\n▶ cspell (${readOnly ? 'перевірка' : 'omlx-автофікс одруків + перевірка'})`)
111
- const cspellCode = runCspellText(process.cwd(), readOnly)
111
+ console.log(`\n▶ cspell (${!readOnly && llmFix ? 'omlx-класифікація + словник + перевірка' : 'перевірка'})`)
112
+ const cspellCode = runCspellText(process.cwd(), readOnly, llmFix)
112
113
  if (cspellCode !== 0) return cspellCode
113
114
 
114
115
  console.log(`\n▶ shellcheck (${readOnly ? 'перевірка' : 'авто-фікс + фінальна перевірка'} *.sh)`)
@@ -129,8 +130,9 @@ function runLintTextSteps(readOnly = false) {
129
130
 
130
131
  /**
131
132
  * Публічна CLI-форма: серіалізує через `withLock('lint-text')` + дедуп за станом git-дерева.
132
- * @param {{ readOnly?: boolean }} [opts] readOnly → детект без авто-фіксу
133
+ * @param {{ readOnly?: boolean, llmFix?: boolean }} [opts] readOnly → детект без авто-фіксу;
134
+ * llmFix → omlx-класифікація cspell (opt-in із `meta.json: llmFix:true`)
133
135
  * @returns {Promise<number>} код виходу
134
136
  */
135
137
  export const runLintTextCli = (opts = {}) =>
136
- runStandardLint(import.meta.dirname, () => runLintTextSteps(opts.readOnly === true))
138
+ runStandardLint(import.meta.dirname, () => runLintTextSteps(opts.readOnly === true, opts.llmFix === true))
@@ -1 +1 @@
1
- { "auto": "завжди", "lint": "per-file" }
1
+ { "auto": "завжди", "lint": "per-file", "llmFix": true }