@nitra/cursor 1.13.83 → 1.13.85

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.
Files changed (96) hide show
  1. package/.claude-template/commands/n-check.md +2 -2
  2. package/CHANGELOG.md +56 -27
  3. package/README.md +10 -10
  4. package/bin/n-cursor.js +40 -60
  5. package/package.json +1 -1
  6. package/rules/abie/abie.mdc +2 -2
  7. package/rules/abie/fix.mjs +5 -1
  8. package/rules/adr/fix.mjs +5 -1
  9. package/rules/adr/js/hooks/check.mjs +1 -1
  10. package/rules/bun/bun.mdc +1 -1
  11. package/rules/bun/fix.mjs +5 -1
  12. package/rules/bun/js/layout/check.mjs +1 -1
  13. package/rules/capacitor/fix.mjs +5 -1
  14. package/rules/capacitor/policy/package_json/package_json.rego +3 -3
  15. package/rules/changelog/changelog.mdc +1 -1
  16. package/rules/changelog/fix.mjs +5 -1
  17. package/rules/ci4/ci4.mdc +1 -1
  18. package/rules/ci4/fix.mjs +5 -1
  19. package/rules/docker/docker.mdc +6 -6
  20. package/rules/docker/fix.mjs +5 -1
  21. package/rules/docker/lint/lint.mjs +10 -3
  22. package/rules/docker/policy/package_json/package_json.rego +1 -1
  23. package/rules/efes/efes.mdc +1 -1
  24. package/rules/efes/fix.mjs +5 -1
  25. package/rules/feedback/feedback.mdc +2 -2
  26. package/rules/feedback/fix.mjs +5 -1
  27. package/rules/ga/fix.mjs +5 -1
  28. package/rules/ga/lint/lint.mjs +5 -5
  29. package/rules/ga/policy/workflow_common/workflow_common.rego +1 -1
  30. package/rules/graphql/fix.mjs +5 -1
  31. package/rules/graphql/policy/vscode_extensions/vscode_extensions.rego +2 -2
  32. package/rules/hasura/fix.mjs +5 -1
  33. package/rules/image-avif/fix.mjs +5 -1
  34. package/rules/image-avif/image-avif.mdc +1 -1
  35. package/rules/image-avif/js/avif_generation/check.mjs +1 -1
  36. package/rules/image-compress/fix.mjs +5 -1
  37. package/rules/image-compress/js/package_setup/check.mjs +1 -1
  38. package/rules/js-bun-db/fix.mjs +5 -1
  39. package/rules/js-bun-redis/fix.mjs +5 -1
  40. package/rules/js-lint/fix.mjs +5 -1
  41. package/rules/js-lint/js/tooling/check.mjs +2 -2
  42. package/rules/js-lint/js-lint.mdc +1 -1
  43. package/rules/js-mssql/fix.mjs +5 -1
  44. package/rules/js-mssql/policy/package_json/package_json.rego +2 -2
  45. package/rules/js-run/fix.mjs +5 -1
  46. package/rules/js-run/js/runtime/check.mjs +3 -3
  47. package/rules/k8s/fix.mjs +5 -1
  48. package/rules/k8s/js/manifests/check.mjs +1 -1
  49. package/rules/k8s/k8s.mdc +12 -12
  50. package/rules/k8s/lint/lint.mjs +12 -5
  51. package/rules/k8s/policy/base_kustomization/base_kustomization.rego +3 -3
  52. package/rules/k8s/policy/base_manifest/base_manifest.rego +2 -2
  53. package/rules/k8s/policy/gateway/gateway.rego +2 -2
  54. package/rules/k8s/policy/hasura_configmap/hasura_configmap.rego +3 -3
  55. package/rules/k8s/policy/hasura_httproute/hasura_httproute.rego +1 -1
  56. package/rules/k8s/policy/hpa_pdb/hpa_pdb.rego +1 -1
  57. package/rules/k8s/policy/kustomization/kustomization.rego +2 -2
  58. package/rules/k8s/policy/manifest/manifest.rego +4 -4
  59. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml.rego +2 -2
  60. package/rules/k8s/policy/svc_yaml/svc_yaml.rego +2 -2
  61. package/rules/nginx-default-tpl/fix.mjs +5 -1
  62. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.rego +2 -2
  63. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.rego +1 -1
  64. package/rules/npm-module/fix.mjs +5 -1
  65. package/rules/npm-module/js/package_structure/check.mjs +2 -2
  66. package/rules/php/fix.mjs +5 -1
  67. package/rules/php/js/tooling/check.mjs +2 -2
  68. package/rules/rego/fix.mjs +5 -1
  69. package/rules/rego/lint/lint.mjs +12 -2
  70. package/rules/security/fix.mjs +5 -1
  71. package/rules/security/security.mdc +1 -1
  72. package/rules/style-lint/fix.mjs +5 -1
  73. package/rules/style-lint/js/tooling/check.mjs +2 -2
  74. package/rules/tauri/fix.mjs +5 -1
  75. package/rules/tauri/js/tooling/check.mjs +1 -1
  76. package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +2 -2
  77. package/rules/test/fix.mjs +5 -1
  78. package/rules/test/test.mdc +1 -1
  79. package/rules/text/fix.mjs +5 -1
  80. package/rules/text/js/formatting/check.mjs +2 -2
  81. package/rules/text/lint/lint.mjs +9 -2
  82. package/rules/text/text.mdc +1 -1
  83. package/rules/vue/fix.mjs +5 -1
  84. package/rules/vue/js/packages/check.mjs +1 -1
  85. package/rules/vue/policy/package_json/package_json.rego +1 -1
  86. package/rules/vue/vue.mdc +1 -1
  87. package/schemas/n-cursor.json +1 -1
  88. package/scripts/build-agents-commands.mjs +1 -1
  89. package/scripts/claude-stop-hook.mjs +1 -1
  90. package/scripts/utils/discover-check-rules-from-cursor.mjs +1 -1
  91. package/scripts/utils/read-n-cursor-config-lite.mjs +59 -0
  92. package/scripts/utils/run-rule-cli.mjs +37 -0
  93. package/scripts/utils/run-standard-rule.mjs +12 -3
  94. package/scripts/utils/with-lock.mjs +2 -2
  95. package/skills/fix/SKILL.md +5 -5
  96. package/skills/lint/SKILL.md +1 -1
@@ -28,14 +28,14 @@ export function check() {
28
28
  }
29
29
 
30
30
  if (existsSync('package.json')) {
31
- pass('package.json є (наявність lint-php перевіряє npx @nitra/cursor check → php.package_json)')
31
+ pass('package.json є (наявність lint-php перевіряє npx @nitra/cursor fix → php.package_json)')
32
32
  } else {
33
33
  fail('package.json не знайдено в корені — додай (php.mdc)')
34
34
  }
35
35
 
36
36
  const wfPath = '.github/workflows/lint-php.yml'
37
37
  if (existsSync(wfPath)) {
38
- pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor check → php.lint_php_yml)`)
38
+ pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor fix → php.lint_php_yml)`)
39
39
  } else {
40
40
  fail(`${wfPath} не існує — створи згідно php.mdc`)
41
41
  }
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -29,6 +29,7 @@ import { resolve } from 'node:path'
29
29
 
30
30
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
31
31
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
32
+ import { withLock } from '../../../scripts/utils/with-lock.mjs'
32
33
 
33
34
  /** Шляхи з Rego-полісі (відносно cwd). Існують не всі на ранніх стадіях — фільтруємо нижче. */
34
35
  const LINT_TARGETS = ['npm/rules']
@@ -89,10 +90,13 @@ function runStep(bin, args, cwd) {
89
90
  /**
90
91
  * Запускає `opa check --strict` і `regal lint` по існуючих цілях. Якщо жодної цілі немає —
91
92
  * пропускає лінт із кодом 0. Якщо хоча б один preflight не пройшов — exit 1 ще до запусків.
93
+ *
94
+ * Внутрішня форма без локу — для тестів, які працюють у тимчасових каталогах і мають
95
+ * можливість запускати fresh без дедуплікації проти попереднього прогону.
92
96
  * @param {string} [cwd] робочий каталог (за замовчуванням `process.cwd()`)
93
97
  * @returns {number} 0 — OK або skip; інакше код виходу першого кроку, що впав
94
98
  */
95
- export function runLintRego(cwd = process.cwd()) {
99
+ export function runLintRegoSteps(cwd = process.cwd()) {
96
100
  const root = resolve(cwd)
97
101
  const opa = resolveCmd('opa')
98
102
  const regal = resolveCmd('regal')
@@ -130,6 +134,12 @@ export function runLintRego(cwd = process.cwd()) {
130
134
  return runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
131
135
  }
132
136
 
137
+ /**
138
+ * Публічна CLI-форма: серіалізує через `withLock('lint-rego')` + дедуп за станом git-дерева.
139
+ * @returns {Promise<number>} код виходу
140
+ */
141
+ export const runLintRego = () => withLock('lint-rego', () => runLintRegoSteps())
142
+
133
143
  if (isRunAsCli()) {
134
- process.exitCode = runLintRego()
144
+ process.exitCode = await runLintRego()
135
145
  }
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -49,4 +49,4 @@ Workflow обовʼязковий — забезпечує незалежний
49
49
 
50
50
  ## Перевірка
51
51
 
52
- `npx @nitra/cursor check security`
52
+ `npx @nitra/cursor fix security`
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -44,7 +44,7 @@ async function checkStylelintConfigPresence(reporter) {
44
44
  // `.vscode/extensions.json` (`stylelint.vscode-stylelint`) і `.vscode/settings.json`
45
45
  // (`css.validate`/`scss.validate`/`less.validate: false`) — у rego-пакетах
46
46
  // `style_lint.vscode_extensions` і `style_lint.vscode_settings`, прогоняє
47
- // `npx @nitra/cursor check`. JS-копії видалено, щоб не було двох джерел істини.
47
+ // `npx @nitra/cursor fix`. JS-копії видалено, щоб не було двох джерел істини.
48
48
 
49
49
  /**
50
50
  * Перевіряє відповідність проєкту правилам style-lint.mdc
@@ -64,7 +64,7 @@ export async function check() {
64
64
 
65
65
  const wfPath = '.github/workflows/lint-style.yml'
66
66
  if (existsSync(wfPath)) {
67
- pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor check → style_lint.lint_style_yml)`)
67
+ pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor fix → style_lint.lint_style_yml)`)
68
68
  } else {
69
69
  fail(`${wfPath} не існує — створи його`)
70
70
  }
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -12,7 +12,7 @@
12
12
  * 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
13
13
  * content `rego.tauri.vscode_extensions` через `runConftestBatch`.
14
14
  *
15
- * Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`) — це conditional
15
+ * Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor fix`) — це conditional
16
16
  * правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
17
17
  */
18
18
  import { existsSync, statSync } from 'node:fs'
@@ -1,9 +1,9 @@
1
1
  # Перевірка `.vscode/extensions.json` для tauri (tauri.mdc).
2
2
  #
3
- # Викликається з `check-tauri.mjs` через `runConftestBatch` лише ПІСЛЯ того,
3
+ # Викликається з `rules/tauri/fix.mjs` через `runConftestBatch` лише ПІСЛЯ того,
4
4
  # як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
5
5
  # у будь-якому пакеті, або залежність `@tauri-apps/*`). Без `target.json` поруч
6
- # (не auto-discoverable через `n-cursor check`) — інакше false-positive порушення на не-Tauri проєктах.
6
+ # (не auto-discoverable через `n-cursor fix`) — інакше false-positive порушення на не-Tauri проєктах.
7
7
  #
8
8
  # Canonical (tauri.mdc): `recommendations` має містити обидва записи —
9
9
  # - tauri-apps.tauri-vscode
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -53,7 +53,7 @@ Recursive globs ловлять файли всередині `tests/` так с
53
53
 
54
54
  ## Що перевіряє правило
55
55
 
56
- `npx @nitra/cursor check test` (concern `location`) проходить деревом пакета й перевіряє: **кожен `*.test.mjs` файл лежить усередині каталогу з ім'ям `tests`** (точне співпадіння басенейму батьківської директорії). Файли поза цим каталогом репортуються з порадою куди їх перенести.
56
+ `npx @nitra/cursor fix test` (concern `location`) проходить деревом пакета й перевіряє: **кожен `*.test.mjs` файл лежить усередині каталогу з ім'ям `tests`** (точне співпадіння басенейму батьківської директорії). Файли поза цим каталогом репортуються з порадою куди їх перенести.
57
57
 
58
58
  `*_test.rego` перевіркою **не охоплюються** — вони не переміщуються.
59
59
 
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -11,7 +11,7 @@
11
11
  * - абзац про український апостроф у `.cursor/rules/n-text.mdc` /
12
12
  * `npm/mdc/text.mdc` (markdown-текст, не JSON/YAML);
13
13
  * - складна валідація скрипта `lint-text` (cspell, markdownlint, v8r у трьох
14
- * варіантах, run-shellcheck-text.mjs, обовʼязкові glob-и);
14
+ * варіантах, run-shellrules/text/fix.mjs, обовʼязкові glob-и);
15
15
  * - workflow `lint-text.yml` має крок `bun run lint-text` (структура — rego `text.lint_text`).
16
16
  *
17
17
  * **Що покрила Rego** (`npx \@nitra/cursor check`):
@@ -111,7 +111,7 @@ function checkTextConfigsExistence(passFn, failFn) {
111
111
  ['.vscode/settings.json', 'text.vscode_settings']
112
112
  ]) {
113
113
  if (existsSync(path)) {
114
- passFn(`${path} є (структуру перевіряє npx @nitra/cursor check → ${mdcRef})`)
114
+ passFn(`${path} є (структуру перевіряє npx @nitra/cursor fix → ${mdcRef})`)
115
115
  } else {
116
116
  failFn(`${path} не існує — створи згідно n-text.mdc`)
117
117
  }
@@ -18,6 +18,7 @@ import { platform } from 'node:process'
18
18
 
19
19
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
20
20
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
21
+ import { withLock } from '../../../scripts/utils/with-lock.mjs'
21
22
  import { runDotenvLinter } from './run-dotenv-linter.mjs'
22
23
  import { runShellcheckText } from './run-shellcheck.mjs'
23
24
  import { runV8rWithGlobs } from './run-v8r.mjs'
@@ -120,10 +121,10 @@ function preflight(dep) {
120
121
  }
121
122
 
122
123
  /**
123
- * Виконує канонічний `lint-text` з preflight і ланцюжком cspell → shellcheck → dotenv → markdownlint → v8r.
124
+ * Внутрішні кроки `lint-text` без локу.
124
125
  * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
125
126
  */
126
- export function runLintTextCli() {
127
+ function runLintTextSteps() {
127
128
  let preflightOk = true
128
129
  for (const dep of [SHELLCHECK_PREFLIGHT, PATCH_PREFLIGHT, DOTENV_LINTER_PREFLIGHT]) {
129
130
  if (!preflight(dep)) preflightOk = false
@@ -147,3 +148,9 @@ export function runLintTextCli() {
147
148
  console.log('\n▶ v8r (schema-валідація json/json5/yaml/yml/toml)')
148
149
  return runV8rWithGlobs()
149
150
  }
151
+
152
+ /**
153
+ * Публічна CLI-форма: серіалізує через `withLock('lint-text')` + дедуп за станом git-дерева.
154
+ * @returns {Promise<number>} код виходу
155
+ */
156
+ export const runLintTextCli = () => withLock('lint-text', () => runLintTextSteps())
@@ -260,4 +260,4 @@ kebab-case
260
260
 
261
261
  ## Перевірка
262
262
 
263
- `npx @nitra/cursor check text` (охоплює oxfmt, cspell, shellcheck у `lint-text`, markdownlint, v8r, CI для `lint-text`)
263
+ `npx @nitra/cursor fix text` (охоплює oxfmt, cspell, shellcheck у `lint-text`, markdownlint, v8r, CI для `lint-text`)
package/rules/vue/fix.mjs CHANGED
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -413,7 +413,7 @@ async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, ha
413
413
  */
414
414
  async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
415
415
  const prefix = `[${packageLabel(rootDir)}] `
416
- passFn(`${prefix}package.json залежності перевіряє npx @nitra/cursor check → vue.package_json`)
416
+ passFn(`${prefix}package.json залежності перевіряє npx @nitra/cursor fix → vue.package_json`)
417
417
 
418
418
  await checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail)
419
419
 
@@ -1,5 +1,5 @@
1
1
  # Порт перевірки версій з `package.json` для Vue+Vite пакетів з
2
- # `npm/scripts/check-vue.mjs` (vue.mdc).
2
+ # `npm/scripts/rules/vue/fix.mjs` (vue.mdc).
3
3
  #
4
4
  # Запуск (локально, у Vue+Vite-пакеті):
5
5
  # conftest test path/to/package.json -p npm/policy/vue \
package/rules/vue/vue.mdc CHANGED
@@ -341,4 +341,4 @@ import path from 'node:path'
341
341
 
342
342
  ## Перевірка
343
343
 
344
- `npx @nitra/cursor check vue` — перевіряє залежності, `vite.config`, наявність **`src/vite-env.d.ts`** з `/// <reference types="vite/client" />` та **`jsconfig.json`** у корені Vue-пакета; обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім’я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/rules/vue/js/packages/vue-forbidden-imports.mjs`).
344
+ `npx @nitra/cursor fix vue` — перевіряє залежності, `vite.config`, наявність **`src/vite-env.d.ts`** з `/// <reference types="vite/client" />` та **`jsconfig.json`** у корені Vue-пакета; обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім’я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/rules/vue/js/packages/vue-forbidden-imports.mjs`).
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "ignore": {
47
47
  "type": "array",
48
- "description": "Директорії, що повністю виключаються з обходу check-скриптів CLI (npx @nitra/cursor check ...) і AI-модифікацій (AI не редагує, не видаляє, не створює файли). Шляхи відносно кореня репозиторію (posix). Приклади: vendored Helm-чарти, генеровані маніфести, legacy-дерева. Стандартні виключення (node_modules, .git, dist, coverage, .turbo, .next) застосовуються завжди — додавати їх не треба.",
48
+ "description": "Директорії, що повністю виключаються з обходу check-скриптів CLI (npx @nitra/cursor fix ...) і AI-модифікацій (AI не редагує, не видаляє, не створює файли). Шляхи відносно кореня репозиторію (posix). Приклади: vendored Helm-чарти, генеровані маніфести, legacy-дерева. Стандартні виключення (node_modules, .git, dist, coverage, .turbo, .next) застосовуються завжди — додавати їх не треба.",
49
49
  "items": {
50
50
  "type": "string",
51
51
  "minLength": 1
@@ -81,7 +81,7 @@ export async function buildAgentsCommandBulletItems(projectRoot) {
81
81
  {
82
82
  name: `- **Оновити правила та ${AGENTS_MD}** (після змін у правилах/шаблоні CLI): \`npx ${PACKAGE_NAME}\``
83
83
  },
84
- { name: `- **Перевірки правил (programmatic)**: \`npx ${PACKAGE_NAME} check\`` },
84
+ { name: `- **Перевірки правил (programmatic)**: \`npx ${PACKAGE_NAME} fix\`` },
85
85
  { name: `- **knip (невикористані залежності та експорти)**: \`bunx knip\`` }
86
86
  )
87
87
 
@@ -68,7 +68,7 @@ export async function runStopHookCli() {
68
68
  const [code] = await once(child, 'exit')
69
69
  return code ?? 1
70
70
  } catch (error) {
71
- process.stderr.write(`stop-hook: не вдалося запустити npx @nitra/cursor check — ${error.message}\n`)
71
+ process.stderr.write(`stop-hook: не вдалося запустити npx @nitra/cursor fix — ${error.message}\n`)
72
72
  return 1
73
73
  }
74
74
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Визначає список id правил для `npx @nitra/cursor check` без аргументів:
2
+ * Визначає список id правил для `npx @nitra/cursor fix` без аргументів:
3
3
  * зчитує базові імена `*.mdc` у `.cursor/rules/` і залишає лише ті id,
4
4
  * для яких у пакеті є programmatic перевірка (JS-концерн або policy з target.json).
5
5
  */
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Light read-only `.n-cursor.json` reader для standalone `fix.mjs` invocation.
3
+ *
4
+ * НЕ робить auto-rules detection, merge, schema sync — це справа повного `readConfig` у CLI.
5
+ * Тут лише: прочитати файл (якщо є), повернути `{ rules: string[], disableRules: string[] }`.
6
+ *
7
+ * Спостереження whitelist:
8
+ * - якщо `.n-cursor.json` НЕМАЄ → правило вважається enabled (поведінка "open by default"),
9
+ * щоб `bun rules/<id>/fix.mjs` з будь-якої тимчасової директорії працювало для debug.
10
+ * - якщо файл є з `rules:[…]`, але правила там немає → правило не enabled.
11
+ * - якщо правило в `disable-rules` → не enabled, навіть якщо у `rules:[…]`.
12
+ */
13
+ import { existsSync } from 'node:fs'
14
+ import { readFile } from 'node:fs/promises'
15
+ import { join } from 'node:path'
16
+
17
+ const CONFIG_FILE = '.n-cursor.json'
18
+
19
+ /**
20
+ * @typedef {object} LiteConfig
21
+ * @property {boolean} exists чи існує .n-cursor.json у поточному каталозі
22
+ * @property {string[]} rules id правил з whitelist (порожній якщо файл відсутній)
23
+ * @property {string[]} disableRules id правил, явно вимкнених у `disable-rules`
24
+ */
25
+
26
+ /**
27
+ * @param {string} [cwd] корінь, у якому шукати .n-cursor.json (default — `process.cwd()`)
28
+ * @returns {Promise<LiteConfig>} стан конфігу
29
+ */
30
+ export async function readNCursorConfigLite(cwd = process.cwd()) {
31
+ const configPath = join(cwd, CONFIG_FILE)
32
+ if (!existsSync(configPath)) {
33
+ return { exists: false, rules: [], disableRules: [] }
34
+ }
35
+ const raw = await readFile(configPath, 'utf8')
36
+ /** @type {{ rules?: unknown, ['disable-rules']?: unknown }} */
37
+ const parsed = JSON.parse(raw)
38
+ const rules = Array.isArray(parsed.rules) ? parsed.rules.filter(r => typeof r === 'string') : []
39
+ const disableRules = Array.isArray(parsed['disable-rules'])
40
+ ? parsed['disable-rules'].filter(r => typeof r === 'string')
41
+ : []
42
+ return { exists: true, rules, disableRules }
43
+ }
44
+
45
+ /**
46
+ * Чи активне правило згідно з конфігом.
47
+ * - файл відсутній → true (open by default для debug);
48
+ * - правило явно в `disable-rules` → false;
49
+ * - правило у `rules` → true;
50
+ * - інакше → false.
51
+ * @param {LiteConfig} config розпарсений lite-конфіг
52
+ * @param {string} ruleId id правила (= basename каталогу)
53
+ * @returns {boolean} чи запускати правило
54
+ */
55
+ export function isRuleEnabled(config, ruleId) {
56
+ if (!config.exists) return true
57
+ if (config.disableRules.includes(ruleId)) return false
58
+ return config.rules.includes(ruleId)
59
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Standalone CLI runner для одного правила. Викликається з `rules/<id>/fix.mjs`
3
+ * у блоці `if (import.meta.main)` — це робить `bun rules/<id>/fix.mjs` повним
4
+ * еквівалентом старого `npx @nitra/cursor fix <id>`: читає `.n-cursor.json`,
5
+ * перевіряє whitelist, друкує summary, повертає aggregated exit-code.
6
+ *
7
+ * Library-mode виклик з CLI orchestration — інше: див. `runStandardRule` + `fix.mjs::run(ctx)`.
8
+ */
9
+ import { basename } from 'node:path'
10
+
11
+ import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
12
+ import { runStandardRule } from './run-standard-rule.mjs'
13
+ import { getOrCreateWalkCache } from './walk-cache.mjs'
14
+
15
+ const PACKAGE_NAME = '@nitra/cursor'
16
+
17
+ /**
18
+ * @param {string} ruleDir абсолютний шлях до `rules/<id>/`
19
+ * @returns {Promise<number>} 0 — OK або правило не enabled; 1 — порушення
20
+ */
21
+ export async function runRuleCli(ruleDir) {
22
+ const ruleId = basename(ruleDir)
23
+ const config = await readNCursorConfigLite()
24
+
25
+ if (!isRuleEnabled(config, ruleId)) {
26
+ console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — правило не в \`.n-cursor.json:rules\`. Пропущено.\n`)
27
+ return 0
28
+ }
29
+
30
+ console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — перевірка правила\n`)
31
+
32
+ const walkCache = getOrCreateWalkCache()
33
+ const exitCode = await runStandardRule(ruleDir, { walkCache })
34
+ const ok = exitCode === 0
35
+ console.log(`\n✨ Результат: ${ok ? 1 : 0}/1 правил без зауважень\n`)
36
+ return exitCode
37
+ }
@@ -3,12 +3,19 @@
3
3
  *
4
4
  * Інкапсулює: `discoverOneRule` → `runRule(applies → JS → policy → mdc-refs)`.
5
5
  * Локальна логіка в правилах заборонена; розширення поведінки — через `ctx`-опції.
6
+ *
7
+ * Серіалізація: загортає виконання у `withLock('fix-<ruleId>')` — паралельні запуски
8
+ * того самого правила (через `npx @nitra/cursor fix`, прямий `bun rules/<id>/fix.mjs`
9
+ * чи `run(ctx)`-композицію) дедупляться за станом git-дерева; різні правила можуть
10
+ * виконуватись паралельно. Точка інтеграції — тут, щоб не дублювати лок у кожному
11
+ * `fix.mjs`.
6
12
  */
7
13
  import { basename, dirname } from 'node:path'
8
14
 
9
15
  import { discoverOneRule } from './discover-checkable-rules.mjs'
10
16
  import { runRule } from './run-rule.mjs'
11
17
  import { getOrCreateWalkCache } from './walk-cache.mjs'
18
+ import { withLock } from './with-lock.mjs'
12
19
 
13
20
  /**
14
21
  * @typedef {object} RuleContext
@@ -28,7 +35,9 @@ import { getOrCreateWalkCache } from './walk-cache.mjs'
28
35
  export async function runStandardRule(ruleDir, ctx = {}) {
29
36
  const ruleId = basename(ruleDir)
30
37
  const bundledRulesDir = dirname(ruleDir)
31
- const rule = await discoverOneRule(ruleDir, ruleId)
32
- const walkCache = ctx.walkCache ?? getOrCreateWalkCache()
33
- return runRule(rule, bundledRulesDir, walkCache)
38
+ return withLock(`fix-${ruleId}`, async () => {
39
+ const rule = await discoverOneRule(ruleDir, ruleId)
40
+ const walkCache = ctx.walkCache ?? getOrCreateWalkCache()
41
+ return runRule(rule, bundledRulesDir, walkCache)
42
+ })
34
43
  }
@@ -40,8 +40,8 @@ export function shouldDedup(result, fingerprint, ttl) {
40
40
 
41
41
  /**
42
42
  * @param {string} key
43
- * @param {() => Promise<number>} runFn
44
- * @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string}} [opts]
43
+ * @param {() => number | Promise<number>} runFn
44
+ * @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string, getFingerprint?:() => string | null}} [opts]
45
45
  * @returns {Promise<number>}
46
46
  */
47
47
  export async function withLock(key, runFn, opts = {}) {
@@ -8,14 +8,14 @@ description: >-
8
8
 
9
9
  ## Scope
10
10
 
11
- Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor check` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`). Не запускай `bun run lint` із цього скілу і не намагайся виправляти його порушення тут — це задача `/n-lint`. Якщо `npx @nitra/cursor check` чистий, а `bun run lint` лишився червоним — запусти `/n-lint` окремо.
11
+ Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor fix` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`). Не запускай `bun run lint` із цього скілу і не намагайся виправляти його порушення тут — це задача `/n-lint`. Якщо `npx @nitra/cursor fix` чистий, а `bun run lint` лишився червоним — запусти `/n-lint` окремо.
12
12
 
13
13
  ## Workflow
14
14
 
15
- 1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor check bun ga …`):
15
+ 1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor fix bun ga …`):
16
16
 
17
17
  ```bash
18
- npx @nitra/cursor check
18
+ npx @nitra/cursor fix
19
19
  ```
20
20
 
21
21
  2. **Аналіз** — зчитай вивід, знайди всі `❌` та визнач які правила порушено
@@ -43,7 +43,7 @@ oxfmt .
43
43
  6. **Верифікація** — перевір що все виправлено:
44
44
 
45
45
  ```bash
46
- npx @nitra/cursor check
46
+ npx @nitra/cursor fix
47
47
  ```
48
48
 
49
- 7. **Результат** — всі `❌` від `npx @nitra/cursor check` мають стати `✅`. Якщо залишились `❌` — повтори кроки 3-6. Лінт-помилки від `bun run lint` тут **не виправляй** — вони на скіл `/n-lint`.
49
+ 7. **Результат** — всі `❌` від `npx @nitra/cursor fix` мають стати `✅`. Якщо залишились `❌` — повтори кроки 3-6. Лінт-помилки від `bun run lint` тут **не виправляй** — вони на скіл `/n-lint`.
@@ -114,4 +114,4 @@ bun run lint
114
114
 
115
115
  ## Примітка
116
116
 
117
- Цей скіл **не** замінює **`npx @nitra/cursor check`**: **`lint`** перевіряє лінтери/формат у **`package.json`**, а **`check`** — програмні правила пакета **`@nitra/cursor`**. За потреби запускай обидва.
117
+ Цей скіл **не** замінює **`npx @nitra/cursor fix`**: **`lint`** перевіряє лінтери/формат у **`package.json`**, а **`check`** — програмні правила пакета **`@nitra/cursor`**. За потреби запускай обидва.