@nitra/cursor 1.13.85 → 1.13.87

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
@@ -4,6 +4,23 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.87] - 2026-05-23
8
+
9
+ ### Added
10
+
11
+ - **`scripts/utils/run-standard-lint.mjs`** — спільна точка входу для всіх `lint-<rule>` підкоманд, дзеркально до `runStandardRule` для `fix-<id>`. Виводить ключ локу зі шляху (`basename(dirname(lintDir))`) і прокидає `opts` у `withLock`. Місце для майбутніх крос-cutting розширень (телеметрія, env-toggle вимкнення локу, common preflight-логування) — патчиш одне місце, не 5 файлів.
12
+
13
+ ### Changed
14
+
15
+ - **5 `rules/<rule>/lint/lint.mjs` (ga, rego, text, k8s, docker)** більше не імпортують `withLock` напряму — використовують `runStandardLint(import.meta.dirname, runLint<Foo>Steps)`. Ім'я правила в одному місці — у каталозі.
16
+ - **`.cursor/rules/scripts.mdc` 1.9 → 1.10:** канон патерну переписано на `runStandardLint` (а не прямий `withLock`); додано явну заборону імпортувати `withLock` у `rules/<rule>/lint/lint.mjs`. У кожному з 5 lint.mjs у top-JSDoc додано посилання «Канон патерну `lint-*` — `.cursor/rules/scripts.mdc`».
17
+
18
+ ## [1.13.86] - 2026-05-23
19
+
20
+ ### Fixed
21
+
22
+ - **`worktreeFingerprint` повертав `null` при untracked-файлах з не-ASCII іменами:** `git ls-files --others --exclude-standard` без `-z` повертає такі шляхи у C-escape виді (`"docs/adr/20260523-...кирилиця..."` з `\321\201`-послідовностями), і наступний `git hash-object <escaped>` не знаходить файл — увесь fingerprint падав у `null`, через що дедуп ніколи не спрацьовував у репах з кирилицею в untracked-іменах. Перехід на `-z` + `\0`-розбиття дає сирий байтовий шлях.
23
+
7
24
  ## [1.13.85] - 2026-05-23
8
25
 
9
26
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.85",
3
+ "version": "1.13.87",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -6,6 +6,9 @@
6
6
  * Dockerfile та варіанти виду app.Dockerfile (регістр суфікса не важливий).
7
7
  *
8
8
  * Виклик hadolint — через ../js/lint/docker-hadolint.mjs (PATH або docker run).
9
+ *
10
+ * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
11
+ * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
9
12
  */
10
13
  import { basename } from 'node:path'
11
14
 
@@ -14,7 +17,7 @@ import { lintDockerfileWithHadolint, posixRel } from '../js/lint/docker-hadolint
14
17
  import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
15
18
  import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
16
19
  import { walkDir } from '../../../scripts/utils/walkDir.mjs'
17
- import { withLock } from '../../../scripts/utils/with-lock.mjs'
20
+ import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
18
21
 
19
22
  /**
20
23
  * Чи входить файл до набору lint-docker: Dockerfile або *.Dockerfile (*.dockerfile).
@@ -85,7 +88,7 @@ async function runLintDockerSteps() {
85
88
  * Експортовано як `runLintDocker` — використовується з `bin/n-cursor.js` як підкоманда `lint-docker`.
86
89
  * @returns {Promise<number>} код виходу
87
90
  */
88
- export const runLintDocker = () => withLock('lint-docker', runLintDockerSteps)
91
+ export const runLintDocker = () => runStandardLint(import.meta.dirname, runLintDockerSteps)
89
92
 
90
93
  if (isRunAsCli()) {
91
94
  process.exitCode = await runLintDocker()
@@ -23,13 +23,16 @@
23
23
  * локально це виглядало як мовчазний exit 1.
24
24
  *
25
25
  * Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
26
+ *
27
+ * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
28
+ * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
26
29
  */
27
30
  import { platform } from 'node:process'
28
31
 
29
32
  import { check as checkGa } from '../js/workflows/check.mjs'
30
33
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
31
34
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
32
- import { withLock } from '../../../scripts/utils/with-lock.mjs'
35
+ import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
33
36
 
34
37
  /**
35
38
  * Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
@@ -165,4 +168,4 @@ async function runLintGaSteps() {
165
168
  return await checkGa()
166
169
  }
167
170
 
168
- export const runLintGaCli = () => withLock('lint-ga', runLintGaSteps)
171
+ export const runLintGaCli = () => runStandardLint(import.meta.dirname, runLintGaSteps)
@@ -11,6 +11,9 @@
11
11
  *
12
12
  * Версія `-kubernetes-version` для kubeconform узгоджена з PIN yannh у rules/k8s/fix.mjs / k8s.mdc.
13
13
  * Kubescape не має аналога цього прапорця; орієнтир цільового кластера — та сама лінія релізу (див. k8s.mdc).
14
+ *
15
+ * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
16
+ * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
14
17
  */
15
18
  import { spawnSync } from 'node:child_process'
16
19
  import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
@@ -24,7 +27,7 @@ import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
24
27
  import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
25
28
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
26
29
  import { walkDir } from '../../../scripts/utils/walkDir.mjs'
27
- import { withLock } from '../../../scripts/utils/with-lock.mjs'
30
+ import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
28
31
 
29
32
  /** Per-project kubescape exceptions file; підмішується через --exceptions, якщо існує в корені. */
30
33
  const KUBESCAPE_EXCEPTIONS_FILE = '.kubescape-exceptions.json'
@@ -349,7 +352,7 @@ async function runLintK8sSteps() {
349
352
  * Експортовано як `runLintK8s` — використовується з `bin/n-cursor.js` як підкоманда `lint-k8s`.
350
353
  * @returns {Promise<number>} код виходу
351
354
  */
352
- export const runLintK8s = () => withLock('lint-k8s', runLintK8sSteps)
355
+ export const runLintK8s = () => runStandardLint(import.meta.dirname, runLintK8sSteps)
353
356
 
354
357
  if (isRunAsCli()) {
355
358
  process.exitCode = await runLintK8s()
@@ -22,6 +22,9 @@
22
22
  * `npm/rules/<id>/policy/<concern>/`). Усі три інструменти приймають один шлях
23
23
  * і самі рекурсивно знаходять `.rego` (ігноруючи інші розширення на кшталт
24
24
  * `target.json` чи template-фіх).
25
+ *
26
+ * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
27
+ * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
25
28
  */
26
29
  import { spawnSync } from 'node:child_process'
27
30
  import { existsSync } from 'node:fs'
@@ -29,7 +32,7 @@ import { resolve } from 'node:path'
29
32
 
30
33
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
31
34
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
32
- import { withLock } from '../../../scripts/utils/with-lock.mjs'
35
+ import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
33
36
 
34
37
  /** Шляхи з Rego-полісі (відносно cwd). Існують не всі на ранніх стадіях — фільтруємо нижче. */
35
38
  const LINT_TARGETS = ['npm/rules']
@@ -138,7 +141,7 @@ export function runLintRegoSteps(cwd = process.cwd()) {
138
141
  * Публічна CLI-форма: серіалізує через `withLock('lint-rego')` + дедуп за станом git-дерева.
139
142
  * @returns {Promise<number>} код виходу
140
143
  */
141
- export const runLintRego = () => withLock('lint-rego', () => runLintRegoSteps())
144
+ export const runLintRego = () => runStandardLint(import.meta.dirname, () => runLintRegoSteps())
142
145
 
143
146
  if (isRunAsCli()) {
144
147
  process.exitCode = await runLintRego()
@@ -13,12 +13,15 @@
13
13
  *
14
14
  * Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
15
15
  * Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
16
+ *
17
+ * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
18
+ * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
16
19
  */
17
20
  import { platform } from 'node:process'
18
21
 
19
22
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
20
23
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
21
- import { withLock } from '../../../scripts/utils/with-lock.mjs'
24
+ import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
22
25
  import { runDotenvLinter } from './run-dotenv-linter.mjs'
23
26
  import { runShellcheckText } from './run-shellcheck.mjs'
24
27
  import { runV8rWithGlobs } from './run-v8r.mjs'
@@ -153,4 +156,4 @@ function runLintTextSteps() {
153
156
  * Публічна CLI-форма: серіалізує через `withLock('lint-text')` + дедуп за станом git-дерева.
154
157
  * @returns {Promise<number>} код виходу
155
158
  */
156
- export const runLintTextCli = () => withLock('lint-text', () => runLintTextSteps())
159
+ export const runLintTextCli = () => runStandardLint(import.meta.dirname, () => runLintTextSteps())
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Спільна точка входу для канонічних `lint-<rule>` підкоманд `@nitra/cursor`.
3
+ *
4
+ * Дзеркально до `runStandardRule` для `fix-<id>`: усі `lint-*` проходять через одну функцію,
5
+ * щоб майбутні крос-cutting концерни (телеметрія, env-toggle вимкнення локу для дебагу,
6
+ * dry-run-режим, common preflight-логування тощо) додавались **в одному** місці, а не
7
+ * патчилися в кожному `rules/<rule>/lint/lint.mjs`.
8
+ *
9
+ * Зараз робить рівно одне: серіалізує + дедуплікує запуски через `withLock('lint-<ruleId>')`.
10
+ * `ruleId` виводиться зі шляху: `import.meta.dirname` у `rules/<id>/lint/lint.mjs` → `<id>`.
11
+ *
12
+ * Інтеграція з боку правила:
13
+ *
14
+ * ```js
15
+ * import { runStandardLint } from '../../../scripts/utils/run-standard-lint.mjs'
16
+ *
17
+ * async function runLintFooSteps() { ... }
18
+ *
19
+ * export const runLintFooCli = () => runStandardLint(import.meta.dirname, runLintFooSteps)
20
+ * ```
21
+ */
22
+ import { basename, dirname } from 'node:path'
23
+
24
+ import { withLock } from './with-lock.mjs'
25
+
26
+ /**
27
+ * @param {string} lintDir абсолютний шлях до `rules/<id>/lint/` (передавай `import.meta.dirname`)
28
+ * @param {() => number | Promise<number>} stepsFn реальна робота лінту; повертає код виходу
29
+ * @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string, getFingerprint?:() => string | null}} [opts] прокидаються у `withLock`
30
+ * @returns {Promise<number>} код виходу
31
+ */
32
+ export function runStandardLint(lintDir, stepsFn, opts) {
33
+ const ruleId = basename(dirname(lintDir))
34
+ return withLock(`lint-${ruleId}`, stepsFn, opts)
35
+ }
@@ -17,8 +17,10 @@ export function worktreeFingerprint(spawn = spawnSync) {
17
17
  try {
18
18
  const commitHash = git(['rev-parse', 'HEAD']).trim()
19
19
  const diffText = git(['diff', 'HEAD'])
20
- const untrackedRaw = git(['ls-files', '--others', '--exclude-standard'])
21
- const untrackedFiles = untrackedRaw.split('\n').filter(Boolean)
20
+ // -z: NUL-розділення без C-екранування. Без нього імена з не-ASCII символами
21
+ // повертаються у `"..."` формі, і `git hash-object` не знаходить файл → throw → fingerprint=null.
22
+ const untrackedRaw = git(['ls-files', '-z', '--others', '--exclude-standard'])
23
+ const untrackedFiles = untrackedRaw.split('\0').filter(Boolean)
22
24
  const pairs = untrackedFiles
23
25
  .map(f => `${f}:${git(['hash-object', f]).trim()}`)
24
26
  .sort()