@nitra/cursor 1.11.4 → 1.11.6

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 (66) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/n-cursor.js +37 -4
  3. package/package.json +2 -1
  4. package/rules/abie/utils/http-route.mjs +1 -1
  5. package/rules/abie/utils/k8s-tree.mjs +9 -10
  6. package/rules/abie/utils/overlay-paths.mjs +1 -1
  7. package/rules/adr/adr.mdc +2 -2
  8. package/rules/adr/js/hooks/check.mjs +5 -5
  9. package/rules/docker/docker.mdc +2 -2
  10. package/rules/docker/js/run.mjs +3 -2
  11. package/rules/docker/policy/package_json/package_json.rego +1 -1
  12. package/rules/ga/js/lint.mjs +3 -26
  13. package/rules/k8s/js/run.mjs +3 -2
  14. package/rules/k8s/k8s.mdc +2 -4
  15. package/rules/npm-module/js/package_structure/check.mjs +2 -2
  16. package/rules/npm-module/npm-module.mdc +3 -3
  17. package/rules/rego/js/lint.mjs +4 -1
  18. package/rules/rego/policy/package_json/package_json.rego +5 -3
  19. package/rules/rego/rego.mdc +3 -3
  20. package/rules/style-lint/js/tooling/check.mjs +1 -1
  21. package/rules/style-lint/style-lint.mdc +1 -1
  22. package/rules/text/js/formatting/check.mjs +8 -24
  23. package/rules/text/js/lint.mjs +34 -0
  24. package/rules/text/js/run-shellcheck.mjs +2 -2
  25. package/rules/text/js/run-v8r.mjs +2 -2
  26. package/rules/text/text.mdc +5 -5
  27. package/schemas/v8r-catalog.json +6 -0
  28. package/scripts/auto-skills.mjs +1 -3
  29. package/scripts/utils/resolve-target-files.mjs +1 -1
  30. package/scripts/utils/run-lint-step.mjs +33 -0
  31. package/scripts/utils/run-rule.mjs +2 -1
  32. package/skills/abie-clean/SKILL.md +9 -5
  33. package/skills/fix/SKILL.md +3 -7
  34. package/rules/abie/policy/base_deployment_preem/base_deployment_preem_test.rego +0 -60
  35. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +0 -48
  36. package/rules/abie/policy/health_check_policy/health_check_policy_test.rego +0 -99
  37. package/rules/abie/policy/http_route_base/http_route_base_test.rego +0 -64
  38. package/rules/bun/policy/package_json/package_json_test.rego +0 -109
  39. package/rules/docker/policy/lint_docker_yml/lint_docker_yml_test.rego +0 -104
  40. package/rules/docker/policy/package_json/package_json_test.rego +0 -42
  41. package/rules/graphql/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  42. package/rules/image-avif/policy/package_json/package_json_test.rego +0 -69
  43. package/rules/js-lint/policy/package_json/package_json_test.rego +0 -130
  44. package/rules/js-run/policy/jsconfig/jsconfig_test.rego +0 -88
  45. package/rules/k8s/policy/base_kustomization/base_kustomization_test.rego +0 -73
  46. package/rules/k8s/policy/base_manifest/base_manifest_test.rego +0 -94
  47. package/rules/k8s/policy/gateway/gateway_test.rego +0 -122
  48. package/rules/k8s/policy/hasura_configmap/hasura_configmap_test.rego +0 -49
  49. package/rules/k8s/policy/hasura_httproute/hasura_httproute_test.rego +0 -148
  50. package/rules/k8s/policy/hpa_pdb/hpa_pdb_test.rego +0 -101
  51. package/rules/k8s/policy/kustomization/kustomization_test.rego +0 -128
  52. package/rules/k8s/policy/manifest/manifest_test.rego +0 -309
  53. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml_test.rego +0 -42
  54. package/rules/k8s/policy/svc_yaml/svc_yaml_test.rego +0 -41
  55. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions_test.rego +0 -30
  56. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings_test.rego +0 -53
  57. package/rules/npm-module/policy/npm_package_json/npm_package_json_test.rego +0 -81
  58. package/rules/rego/policy/package_json/package_json_test.rego +0 -42
  59. package/rules/rego/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  60. package/rules/rego/policy/vscode_settings/vscode_settings_test.rego +0 -55
  61. package/rules/style-lint/policy/vscode_extensions/vscode_extensions_test.rego +0 -39
  62. package/rules/style-lint/policy/vscode_settings/vscode_settings_test.rego +0 -49
  63. package/rules/tauri/policy/vscode_extensions/vscode_extensions_test.rego +0 -44
  64. package/rules/text/policy/markdownlint/markdownlint_test.rego +0 -98
  65. package/rules/text/policy/vscode_extensions/vscode_extensions_test.rego +0 -51
  66. package/rules/text/policy/vscode_settings/vscode_settings_test.rego +0 -85
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.11.6] - 2026-05-15
8
+
9
+ ### Changed
10
+
11
+ - **`npm/rules/npm-module/npm-module.mdc`** — переформульовано вимогу про тести й фікстури. Раніше правило вимагало тримати їх **поза** будь-яким шляхом з `"files"` (канонічно — у `npm/tests/`). Тепер тести/фікстури можуть лежати **поруч з кодом** усередині `"files"`-шляхів, але `"files"` обовʼязково має містити **негативні glob-патерни**, що виключають їх із tarball (`!**/*.test.*`, `!**/*.spec.*`, `!**/test-helpers.*`, `!**/fixtures/**`, `!**/__tests__/**`, опційно `!**/*_test.rego`). Це краще відповідає реальному layout пакета (co-located test-файли у `rules/<id>/js/<concern>/`) і прибирає роз'їзд правила з фактичним `npm/package.json`. Версію `.mdc` піднято до `1.12`.
12
+ - **`npm/rules/npm-module/js/package_structure/check.mjs::checkNoTestsInPublishedFiles`** — текст fail-повідомлення тепер однозначно радить додати негативний glob у `"files"`, без альтернативи «винеси за межі шляхів з "files"». Логіка перевірки (walk positive ∖ negative + класифікація test-style) не змінилась — пере-кваліфіковано лише підказку для агента й людини.
13
+
14
+ ## [1.11.5] - 2026-05-15
15
+
16
+ ### Added
17
+
18
+ - **`npm/bin/n-cursor.js`** — підкоманди `lint-rego`, `lint-k8s`, `lint-docker`, `lint-text` (раніше був лише `lint-ga`). Споживчі `package.json` тепер можуть використовувати уніфіковану форму `n-cursor lint-X` замість прямих посилань на файли `bun ./npm/scripts/*.mjs`, які після phase 2 концерн-сплету переїхали у `npm/rules/<id>/js/`. `lint-text` — композитний: послідовно `cspell .` → `runShellcheckText()` → `bunx markdownlint-cli2 --fix "**/*.md" "**/*.mdc"` → `runV8rWithGlobs()`.
19
+ - **`npm/rules/text/js/lint.mjs`** — новий ентрі-модуль для канонічного `lint-text`.
20
+ - **`npm/scripts/utils/run-lint-step.mjs`** — спільний хелпер `runLintStep(title, cmd, args)` для CLI-обгорток `lint-<rule>` (раніше дублювалося у `rules/ga/js/lint.mjs` і новому `rules/text/js/lint.mjs` — jscpd-clone).
21
+
22
+ ### Changed
23
+
24
+ - **`npm/rules/k8s/js/run.mjs`**, **`npm/rules/docker/js/run.mjs`** — `main()` перейменовано і експортовано як `runLintK8s` / `runLintDocker` для виклику з CLI-маршрутизатора. `isRunAsCli()`-гілка прямого запуску збережена для зворотної сумісності.
25
+ - **`npm/rules/rego/js/lint.mjs`** — авто-виклик `runLintRego()` тепер обгорнуто `if (isRunAsCli())`, інакше імпорт модуля з CLI запускав би лінт як side-effect.
26
+ - **`npm/rules/rego/policy/package_json/`** — канонічне значення `scripts.lint-rego` змінено на `"n-cursor lint-rego"` (раніше `"bun ./npm/scripts/lint-rego.mjs"`). Аналогічно `npm/rules/docker/policy/package_json/` — на `"n-cursor lint-docker"`.
27
+ - **`npm/rules/text/js/formatting/check.mjs`** — `checkLintTextScript()` тепер вимагає рівно `"n-cursor lint-text"` замість попередньої складної валідації багатоступеневого ланцюжка (`cspell` → `run-shellcheck-text.mjs` → `markdownlint-cli2` → `run-v8r.mjs`). Канонічна форма — одне посилання на CLI, а зміст ланцюжка живе у `npm/rules/text/js/lint.mjs`.
28
+ - **`npm/package.json#files`** — додано негативний glob `"!**/*_test.rego"` (раніше були лише `*.test.mjs`, `test-helpers.mjs`, `fixtures/**`). 33 rego-юніт-тестових файли (`<policy>_test.rego`) більше не потрапляють у tarball — їх виконує лише `conftest verify` у dev-репо.
29
+
7
30
  ## [1.11.4] - 2026-05-15
8
31
 
9
32
  ### Fixed
@@ -83,7 +106,7 @@
83
106
 
84
107
  ### Fixed
85
108
 
86
- - `npm/package.json#files`: додано негативні glob-патерни `!**/*.test.mjs`, `!**/test-helpers.mjs`, `!**/fixtures/**`, щоб після переїзду тестів у `rules/<rule>/js/`, `scripts/`, `scripts/utils/` вони не потрапляли в опубліковану npm-таrball (вимагає правило `npm-module`).
109
+ - `npm/package.json#files`: додано негативні glob-патерни `!**/*.test.mjs`, `!**/test-helpers.mjs`, `!**/fixtures/**`, щоб після переїзду тестів у `rules/<rule>/js/`, `scripts/`, `scripts/utils/` вони не потрапляли в опубліковану npm-tarball (вимагає правило `npm-module`).
87
110
  - `npm/package.json#devDependencies`: додано `@nitra/cursor: ^1.9.22` (auto-fill від `ensure-nitra-cursor-dev-dependencies.mjs`).
88
111
 
89
112
  ## [1.9.22] - 2026-05-14
package/bin/n-cursor.js CHANGED
@@ -13,6 +13,12 @@
13
13
  * інакше викликає `check`); прописується автоматично в `.claude/settings.json`
14
14
  * `npx \@nitra/cursor lint-ga` — канонічний lint-ga (ga.mdc): preflight на `shellcheck` →
15
15
  * `bunx github-actionlint` → `uvx zizmor --offline --collect=workflows .`
16
+ * `npx \@nitra/cursor lint-rego` — канонічний lint-rego (conftest.mdc + rego.mdc):
17
+ * preflight на `opa`/`regal` → `opa check --strict` → `regal lint` → опц. `conftest verify`
18
+ * `npx \@nitra/cursor lint-k8s` — канонічний lint-k8s (k8s.mdc): `kubeconform` + `kubescape` по `…/k8s/*.yaml`
19
+ * `npx \@nitra/cursor lint-docker` — канонічний lint-docker (docker.mdc): `hadolint` по `Dockerfile`/`*.Dockerfile`
20
+ * `npx \@nitra/cursor lint-text` — канонічний lint-text (text.mdc): `cspell` → `shellcheck` (з auto-fix) →
21
+ * `markdownlint-cli2 --fix` → `v8r` (json/json5/yaml/yml/toml)
16
22
  *
17
23
  * Claude Code інтеграція: під час синку, окрім `.cursor/rules` і `.claude/commands` (з skills), CLI ще раз
18
24
  * синхронізує `.claude/settings.json` (hooks + permissions; merge — користувацькі поля зберігаються),
@@ -68,7 +74,11 @@ import { detectAutoSkills } from '../scripts/auto-skills.mjs'
68
74
  import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
69
75
  import { discoverCheckableRules } from '../scripts/utils/discover-checkable-rules.mjs'
70
76
  import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
77
+ import { runLintDocker } from '../rules/docker/js/run.mjs'
71
78
  import { runLintGaCli } from '../rules/ga/js/lint.mjs'
79
+ import { runLintK8s } from '../rules/k8s/js/run.mjs'
80
+ import { runLintRego } from '../rules/rego/js/lint.mjs'
81
+ import { runLintTextCli } from '../rules/text/js/lint.mjs'
72
82
  import { runRule } from '../scripts/utils/run-rule.mjs'
73
83
  import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
74
84
  import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
@@ -88,7 +98,6 @@ const RULE_PREFIX = 'n-'
88
98
 
89
99
  const binDir = dirname(fileURLToPath(import.meta.url))
90
100
  const BUNDLED_RULES_DIR = join(binDir, '..', 'rules')
91
- const BUNDLED_SCRIPTS_DIR = join(binDir, '..', 'scripts')
92
101
  const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
93
102
  const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
94
103
  /** Корінь установленого пакету (каталог з `rules/`, `github-actions/`, …) */
@@ -1000,9 +1009,9 @@ function logRemovedManagedItems(title, basePath, names) {
1000
1009
  * (плюс legacy `rules/<id>/js/check.mjs` як концерн `legacy`) або policy-концерн у
1001
1010
  * `rules/<id>/policy/<concern>/target.json`. Делегує у `discoverCheckableRules` —
1002
1011
  * див. `scripts/utils/discover-checkable-rules.mjs`.
1003
- * @returns {Promise<import('../scripts/utils/discover-checkable-rules.mjs').CheckableRule[]>} опис правил у алфавітному порядку
1012
+ * @returns {import('../scripts/utils/discover-checkable-rules.mjs').CheckableRule[]} опис правил у алфавітному порядку
1004
1013
  */
1005
- async function discoverCheckScripts() {
1014
+ function discoverCheckScripts() {
1006
1015
  return discoverCheckableRules(BUNDLED_RULES_DIR)
1007
1016
  }
1008
1017
 
@@ -1348,6 +1357,30 @@ try {
1348
1357
 
1349
1358
  break
1350
1359
  }
1360
+ case 'lint-rego': {
1361
+ // Канонічний lint-rego: preflight opa/regal → opa check --strict → regal lint → conftest verify (опц.).
1362
+ process.exitCode = runLintRego()
1363
+
1364
+ break
1365
+ }
1366
+ case 'lint-k8s': {
1367
+ // Канонічний lint-k8s: kubeconform + kubescape по знайдених деревах `…/k8s/*.yaml`.
1368
+ process.exitCode = await runLintK8s()
1369
+
1370
+ break
1371
+ }
1372
+ case 'lint-docker': {
1373
+ // Канонічний lint-docker: hadolint по Dockerfile та *.Dockerfile (docker.mdc).
1374
+ process.exitCode = await runLintDocker()
1375
+
1376
+ break
1377
+ }
1378
+ case 'lint-text': {
1379
+ // Канонічний lint-text: cspell → run-shellcheck → markdownlint-cli2 --fix → run-v8r (text.mdc).
1380
+ process.exitCode = runLintTextCli()
1381
+
1382
+ break
1383
+ }
1351
1384
  case undefined:
1352
1385
  case '': {
1353
1386
  await runSync()
@@ -1357,7 +1390,7 @@ try {
1357
1390
  default: {
1358
1391
  console.error(`❌ Невідома команда: ${command}`)
1359
1392
  console.error(
1360
- ` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga`
1393
+ ` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text`
1361
1394
  )
1362
1395
  process.exitCode = 1
1363
1396
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.11.4",
3
+ "version": "1.11.6",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -34,6 +34,7 @@
34
34
  "AGENTS.template.md",
35
35
  "CHANGELOG.md",
36
36
  "!**/*.test.mjs",
37
+ "!**/*_test.rego",
37
38
  "!**/test-helpers.mjs",
38
39
  "!**/fixtures/**"
39
40
  ],
@@ -2,7 +2,7 @@
2
2
  * Cross-документна аналітика abie HTTPRoute: підрахунок `backendRefs` до спільних
3
3
  * сервісів (`auth-run-hl`, `file-link-hl`) у base-маніфестах пакета (поза overlay `ua`).
4
4
  * Використовується ua_http_route-концерном для синхронізації числа patch-ів namespace
5
- * у overlay із кількістю base-referencе.
5
+ * у overlay із кількістю base-reference.
6
6
  */
7
7
  import { relative } from 'node:path'
8
8
 
@@ -5,7 +5,6 @@
5
5
  *
6
6
  * Кеш — module-level singleton, ключований за `(root, ignorePaths)`. Перший виклик
7
7
  * платить за обхід; наступні концерни в межах того ж прогону отримують готове.
8
- * Для тестів — `resetAbieK8sTreeCache()` (інакше withTmpCwd-фікстури злипатимуться).
9
8
  */
10
9
  import { dirname, relative } from 'node:path'
11
10
 
@@ -20,15 +19,6 @@ const yamlCache = new Map()
20
19
  /** @type {Map<string, Promise<Set<string>>>} */
21
20
  const deploymentCache = new Map()
22
21
 
23
- /**
24
- * Скидає кеш — тести мусять викликати між фікстурами.
25
- * @returns {void} результат
26
- */
27
- export function resetAbieK8sTreeCache() {
28
- yamlCache.clear()
29
- deploymentCache.clear()
30
- }
31
-
32
22
  /**
33
23
  * Стабільний ключ кешу за (root, ignorePaths).
34
24
  * @param {string} root опис.
@@ -86,6 +76,15 @@ const silentFail = _msg => {
86
76
  // noop
87
77
  }
88
78
 
79
+ /**
80
+ * Знаходить унікальні каталоги, що містять Deployment-маніфести серед переданих YAML-файлів.
81
+ * Парсить документи через `readAndParseYamlDocs` і фільтрує лише ті, що є Deployment.
82
+ * Кешує результат за ключем `root|<sorted yamlAbs>`, щоб повторні виклики не робили I/O.
83
+ * @param {string} root абсолютний корінь репо для побудови relative-шляхів у повідомленнях
84
+ * @param {string[]} yamlAbs абсолютні шляхи до YAML-файлів для перевірки
85
+ * @param {(msg: string) => void} [fail] callback на помилку парсингу (за замовчуванням noop)
86
+ * @returns {Promise<Set<string>>} проміс із сетом абсолютних каталогів, де знайдено Deployment
87
+ */
89
88
  export function collectDeploymentDirs(root, yamlAbs, fail = silentFail) {
90
89
  const key = `${root}|${[...yamlAbs].toSorted((a, b) => a.localeCompare(b)).join(':')}`
91
90
  const cached = deploymentCache.get(key)
@@ -16,7 +16,7 @@ const TRAILING_SLASH_RE = /\/$/u
16
16
 
17
17
  /**
18
18
  * Чи `rel` — це `…/ua/kustomization.yaml` (abie overlay).
19
- * @param {string} rel посі від кореня репозиторію
19
+ * @param {string} rel posix-шлях від кореня репозиторію
20
20
  * @returns {boolean} результат
21
21
  */
22
22
  export function isUaKustomizationPath(rel) {
package/rules/adr/adr.mdc CHANGED
@@ -39,7 +39,7 @@ Stop-hook `normalize-decisions.sh` спрацьовує на тому самом
39
39
  LLM повертає масив операцій:
40
40
 
41
41
  | `op` | Семантика | Поля |
42
- |---|---|---|
42
+ | --- | --- | --- |
43
43
  | `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
44
44
  | `rewrite` | Чернетка стає окремим clean-файлом: frontmatter знімається, ім'я → `<slug>.md`, додаються `**Status: Accepted**` і `**Date:**` з `captured`. | `file`, `slug`, `content` |
45
45
  | `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
@@ -55,7 +55,7 @@ LLM повертає масив операцій:
55
55
  Інший LLM CLI, який запустить normalize, успадковує `ADR_NORMALIZE_RUNNING=1` — внутрішній Stop-hook вийде відразу. Доступні ENV:
56
56
 
57
57
  | Змінна | Default | Призначення |
58
- |---|---|---|
58
+ | --- | --- | --- |
59
59
  | `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
60
60
  | `ADR_NORMALIZE_BATCH` | `30` | Максимум чернеток у одному виклику LLM. |
61
61
  | `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
@@ -79,7 +79,7 @@ function gitignoreLineCoversHookLog(line, logPath) {
79
79
 
80
80
  /**
81
81
  * Перевіряє наявність і канонічність одного hook-скрипта.
82
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
82
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
83
83
  * @param {string} scriptName базове ім'я скрипта (наприклад `capture-decisions.sh`)
84
84
  * @returns {Promise<void>}
85
85
  */
@@ -108,7 +108,7 @@ async function checkHookScript(reporter, scriptName) {
108
108
  * `.claude/settings.local.json`. Структуру (`hooks.Stop[]` містить групу з
109
109
  * `capture-decisions.sh`; `settings.local.json` не дублює) валідують
110
110
  * `npm/policy/adr/settings_json/` і `npm/policy/adr/settings_local_json/`.
111
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер
111
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер
112
112
  */
113
113
  function checkProjectSettings(reporter) {
114
114
  const { pass, fail } = reporter
@@ -121,7 +121,7 @@ function checkProjectSettings(reporter) {
121
121
 
122
122
  /**
123
123
  * Перевіряє `.gitignore` на ігнорування лог-файлу одного хука.
124
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
124
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
125
125
  * @param {string} logName базове ім'я лог-файлу (наприклад `capture-decisions.log`)
126
126
  * @param {string} gitignoreContent попередньо прочитаний вміст `.gitignore`
127
127
  * @returns {void}
@@ -142,7 +142,7 @@ function checkGitignoreForLog(reporter, logName, gitignoreContent) {
142
142
 
143
143
  /**
144
144
  * Перевіряє `.gitignore` для всіх hook-логів одним проходом.
145
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
145
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
146
146
  * @returns {Promise<void>}
147
147
  */
148
148
  async function checkGitignore(reporter) {
@@ -182,7 +182,7 @@ function isBinaryInPath(name) {
182
182
  /**
183
183
  * Інформативна перевірка: чи доступний бодай один LLM CLI (`claude` або `cursor-agent`).
184
184
  * Якщо жодного немає — це warning (`pass` з підказкою), бо хук просто мовчки no-op'ає.
185
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
185
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
186
186
  * @returns {void}
187
187
  */
188
188
  function checkLlmCliAvailable(reporter) {
@@ -97,7 +97,7 @@ CMD ["./app"]
97
97
 
98
98
  ## lint-docker
99
99
 
100
- CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить скрипт **`npm/scripts/run-docker.mjs`**.
100
+ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить CLI **`n-cursor lint-docker`** (реалізація — **`npm/rules/docker/js/run.mjs`**).
101
101
 
102
102
  **Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`check-docker.mjs`).
103
103
 
@@ -106,7 +106,7 @@ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE
106
106
  ```json title="package.json"
107
107
  {
108
108
  "scripts": {
109
- "lint-docker": "bun ./npm/scripts/run-docker.mjs"
109
+ "lint-docker": "n-cursor lint-docker"
110
110
  }
111
111
  }
112
112
  ```
@@ -47,9 +47,10 @@ export async function findLintDockerfilePaths(root, ignorePaths = []) {
47
47
 
48
48
  /**
49
49
  * Запуск hadolint по Dockerfile та *.Dockerfile.
50
+ * Експортовано як `runLintDocker` — використовується з `bin/n-cursor.js` як підкоманда `lint-docker`.
50
51
  * @returns {Promise<number>} 0 — OK, 1 — зауваження або помилка
51
52
  */
52
- async function main() {
53
+ export async function runLintDocker() {
53
54
  const reporter = createCheckReporter()
54
55
  const { pass, fail } = reporter
55
56
 
@@ -80,5 +81,5 @@ async function main() {
80
81
  }
81
82
 
82
83
  if (isRunAsCli()) {
83
- process.exitCode = await main()
84
+ process.exitCode = await runLintDocker()
84
85
  }
@@ -19,7 +19,7 @@ package docker.package_json
19
19
 
20
20
  import rego.v1
21
21
 
22
- canonical_lint_docker := "bun ./npm/scripts/run-docker.mjs"
22
+ canonical_lint_docker := "n-cursor lint-docker"
23
23
 
24
24
  lint_docker_template := concat(" ", [
25
25
  "package.json: scripts.lint-docker має бути %q",
@@ -19,11 +19,11 @@
19
19
  *
20
20
  * Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
21
21
  */
22
- import { spawnSync } from 'node:child_process'
23
22
  import { platform } from 'node:process'
24
23
 
25
24
  import { check as checkGa } from './workflows/check.mjs'
26
25
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
26
+ import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
27
27
 
28
28
  /**
29
29
  * Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
@@ -112,29 +112,6 @@ function preflight(dep) {
112
112
  return false
113
113
  }
114
114
 
115
- /**
116
- * Запускає крок lint-ga з відображенням команди користувачу. Stdout/stderr дочірнього процесу
117
- * передається користувачу як є (`stdio: 'inherit'`), щоб виглядало як прямий виклик у shell.
118
- * @param {string} title заголовок для логу (наприклад `actionlint`)
119
- * @param {string} cmd ім'я команди (`bunx`, `uvx`)
120
- * @param {string[]} args аргументи команди
121
- * @returns {number} код виходу дочірнього процесу (0 — OK, інше — помилка)
122
- */
123
- function runStep(title, cmd, args) {
124
- console.log(`\n▶ ${title}: ${cmd} ${args.join(' ')}`)
125
- const resolved = resolveCmd(cmd)
126
- if (!resolved) {
127
- console.error(`❌ ${cmd} не знайдено в PATH (${title}).`)
128
- return 127
129
- }
130
- const r = spawnSync(resolved, args, { stdio: 'inherit', env: process.env })
131
- if (r.error) {
132
- console.error(`❌ Не вдалося запустити ${cmd}: ${r.error.message}`)
133
- return 1
134
- }
135
- return r.status ?? 1
136
- }
137
-
138
115
  /**
139
116
  * Виконує канонічний `lint-ga` з preflight-перевірками і делегує до `check-ga.check()`.
140
117
  *
@@ -160,10 +137,10 @@ export async function runLintGaCli() {
160
137
  }
161
138
  if (!preflightOk) return 1
162
139
 
163
- const actionlintCode = runStep('actionlint', 'bunx', ['github-actionlint'])
140
+ const actionlintCode = runLintStep('actionlint', 'bunx', ['github-actionlint'])
164
141
  if (actionlintCode !== 0) return actionlintCode
165
142
 
166
- const zizmorCode = runStep('zizmor', 'uvx', ['zizmor', '--offline', '--collect=workflows', '.'])
143
+ const zizmorCode = runLintStep('zizmor', 'uvx', ['zizmor', '--offline', '--collect=workflows', '.'])
167
144
  if (zizmorCode !== 0) return zizmorCode
168
145
 
169
146
  console.log('\n▶ check-ga (rego-полісі npm/policy/ga/ + JS cross-file перевірки)')
@@ -146,9 +146,10 @@ function runKubescape(dirs) {
146
146
 
147
147
  /**
148
148
  * Головна точка входу: kubeconform + kubescape для усіх знайдених дерев `k8s`.
149
+ * Експортовано як `runLintK8s` — використовується з `bin/n-cursor.js` як підкоманда `lint-k8s`.
149
150
  * @returns {Promise<number>} код виходу для `process.exitCode` (0 — успіх або пропуск)
150
151
  */
151
- async function main() {
152
+ export async function runLintK8s() {
152
153
  const root = process.cwd()
153
154
  const ignorePaths = await loadCursorIgnorePaths(root)
154
155
  const dirs = await findK8sRoots(root, ignorePaths)
@@ -169,5 +170,5 @@ async function main() {
169
170
  }
170
171
 
171
172
  if (isRunAsCli()) {
172
- process.exitCode = await main()
173
+ process.exitCode = await runLintK8s()
173
174
  }
package/rules/k8s/k8s.mdc CHANGED
@@ -38,20 +38,18 @@ alwaysApply: false
38
38
 
39
39
  **kubescape:** типово **`kubescape scan <каталог-k8s>`**; поріг серйозності підлаштуй під проєкт (наприклад **`--severity-threshold high`**). Перший запуск може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
40
40
 
41
- У репозиторії пакета **`@nitra/cursor`** скрипт **`lint-k8s`** делегує обхід дерев і виклики **`npm/scripts/run-k8s.mjs`**. У інших проєктах можна скопіювати цей скрипт або зібрати еквівалентні команди в **`package.json`**.
41
+ У репозиторії пакета **`@nitra/cursor`** скрипт **`lint-k8s`** делегує до CLI **`n-cursor lint-k8s`** (реалізація — **`npm/rules/k8s/js/run.mjs`**). У інших проєктах достатньо встановити **`@nitra/cursor`** у `devDependencies` бінарка **`n-cursor`** буде у **`node_modules/.bin/`**.
42
42
 
43
43
  ```json title="package.json"
44
44
  {
45
45
  "scripts": {
46
- "lint-k8s": "bun ./npm/scripts/run-k8s.mjs"
46
+ "lint-k8s": "n-cursor lint-k8s"
47
47
  }
48
48
  }
49
49
  ```
50
50
 
51
51
  Якщо правило **`k8s`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **мають** бути скрипт **`lint-k8s`** і виклик **`bun run lint-k8s`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor check bun`**.
52
52
 
53
- Шлях до скрипта підстав свій (`./scripts/…` після копіювання, `node_modules/@nitra/cursor/scripts/…` якщо пакет у залежностях).
54
-
55
53
  Додай workflow **`.github/workflows/lint-k8s.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**). **Не** дублюй **`setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** і **`bun install`** у job — після **`checkout`** використовуй локальний composite **`setup-bun-deps`** (шлях **`./.github/actions/setup-bun-deps`** або **`./npm/github-actions/setup-bun-deps`**, як у репозиторії). Встановлення **kubeconform** / **kubescape** лишаються окремими кроками.
56
54
 
57
55
  ```yaml title=".github/workflows/lint-k8s.yml"
@@ -496,8 +496,8 @@ async function checkNoTestsInPublishedFiles(pass, fail) {
496
496
  }
497
497
  for (const v of violations) {
498
498
  fail(
499
- `npm/${v.file}: ${v.reason} — винеси за межі шляхів з "files" або додай негативний glob ` +
500
- '(наприклад "!**/*_test.rego") у npm/package.json (npm-module.mdc)'
499
+ `npm/${v.file}: ${v.reason} — додай у "files" у npm/package.json негативний glob, ` +
500
+ 'що виключає цей файл з tarball (наприклад "!**/*.test.mjs", "!**/fixtures/**", "!**/*_test.rego") (npm-module.mdc)'
501
501
  )
502
502
  }
503
503
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Оформлення репозиторію для npm модуля
3
3
  alwaysApply: true
4
- version: '1.11'
4
+ version: '1.12'
5
5
  ---
6
6
 
7
7
  Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.github/workflows/`**; опційно **`demo/`**.
@@ -11,13 +11,13 @@ Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.g
11
11
  Мета — **максимально компактний** опублікований пакет: у npm потрапляє тільки те, що потрібно під час `require`/`import` користувачем.
12
12
 
13
13
  - **`"files"` обовʼязковий** у `npm/package.json` як **whitelist** того, що публікується (без `"files"` npm пакує майже все — це антипатерн для цього правила).
14
- - **Тести й фікстури — поза будь-яким шляхом з `"files"`.** Канонічне місце для всього тестового `npm/tests/` (його **не** додаємо до `"files"`). Це стосується **і** фреймворк-тестів (`bun:test`, `node:test`, `vitest`, `@jest/globals`, `mocha`, …), **і** test-style каталогів (`tests/`, `__tests__/`, `fixtures/`, `__fixtures__/`, `spec/`), **і** файлів за патернами `*.test.*` / `*.spec.*`. **Виняток Rego (`*_test.rego`):** за конвенцією conftest юніт-тест лежить поруч з полісі у тому самому `package`, тож rego-тести дозволені всередині опублікованого `policy/`-каталогу.
14
+ - **Тести й фікстури не публікуються через негативні glob-патерни у `"files"`.** Тести можна зазвичай зручніше) тримати **поруч з кодом** усередині шляхів, перелічених у `"files"` (наприклад `*.test.mjs` поруч з модулем чи `fixtures/` під `rules/<id>/js/`). Щоб вони не потрапляли у tarball, `"files"` **обовʼязково має містити негативні glob-патерни**, що їх виключають. Покрий усі форми тестового: фреймворк-тести (`bun:test`, `node:test`, `vitest`, `@jest/globals`, `mocha`, …), test-style каталоги (`tests/`, `__tests__/`, `fixtures/`, `__fixtures__/`, `spec/`, `test/`), файли за патернами `*.test.*` / `*.spec.*`. Орієнтовний набір: `"!**/*.test.*"`, `"!**/*.spec.*"`, `"!**/test-helpers.*"`, `"!**/fixtures/**"`, `"!**/__tests__/**"`. **Rego (`*_test.rego`):** за конвенцією conftest юніт-тест лежить поруч з полісі у тому самому `package` `*_test.rego` усередині опублікованого `policy/` дозволені; якщо потрібна максимальна компактність, додай `"!**/*_test.rego"` явно (як у самому `@nitra/cursor`).
15
15
  - **Лише runtime-залежності у `npm/package.json`.** `devDependencies` тримай у **кореневому** `package.json` монорепо — тоді `npm install @nitra/<pkg>` не тягне інструментарій, потрібний лише для розробки самого пакета.
16
16
 
17
17
  Деталі алгоритму перевірки:
18
18
 
19
19
  - Пер-документні правила для `npm/package.json` (whitelist `files`, заборона `devDependencies`, форма `types`) — у rego-пакеті `npm_module.npm_package_json` (`npm/policy/npm_module/npm_package_json/`).
20
- - Скан опублікованого простору файлів на тест-патерни (walking + AST JS/TS) — у `check-npm-module.mjs::checkNoTestsInPublishedFiles` (FS / AST не лягають у rego).
20
+ - Walk шляхів з `"files"` з застосуванням негативних patterns і скан залишку на тест-патерни (walking + AST JS/TS) — у `check.mjs::checkNoTestsInPublishedFiles` (FS / AST не лягають у rego). Якщо після застосування негативних patterns у tarball лишається test-style файл — `check` падає з вказівкою, який саме негативний glob треба додати у `"files"`.
21
21
 
22
22
  ## TypeScript declaration (`npm/types`)
23
23
 
@@ -26,6 +26,7 @@ import { spawnSync } from 'node:child_process'
26
26
  import { existsSync } from 'node:fs'
27
27
  import { resolve } from 'node:path'
28
28
 
29
+ import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
29
30
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
30
31
 
31
32
  /** Шляхи з Rego-полісі (відносно cwd). Існують не всі на ранніх стадіях — фільтруємо нижче. */
@@ -128,4 +129,6 @@ export function runLintRego(cwd = process.cwd()) {
128
129
  return runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
129
130
  }
130
131
 
131
- process.exitCode = runLintRego()
132
+ if (isRunAsCli()) {
133
+ process.exitCode = runLintRego()
134
+ }
@@ -3,14 +3,16 @@
3
3
  # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
4
4
  # `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
5
5
  #
6
- # Canonical (rego.mdc): scripts.lint-rego має бути "bun ./npm/scripts/lint-rego.mjs".
6
+ # Canonical (rego.mdc): scripts.lint-rego має бути "n-cursor lint-rego" — CLI пакета `@nitra/cursor`
7
+ # (бінарка з `node_modules/.bin/`) робить preflight `opa`/`regal` і прогонить
8
+ # `opa check --strict` → `regal lint` → опц. `conftest verify` (`@nitra/cursor lint-rego`).
7
9
  #
8
10
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
9
11
  package rego.package_json
10
12
 
11
13
  import rego.v1
12
14
 
13
- canonical_lint_rego := "bun ./npm/scripts/lint-rego.mjs"
15
+ canonical_lint_rego := "n-cursor lint-rego"
14
16
 
15
17
  lint_rego_template := concat(" ", [
16
18
  "package.json: scripts.lint-rego має бути %q",
@@ -22,7 +24,7 @@ deny contains msg if {
22
24
  not "lint-rego" in object.keys(scripts)
23
25
  msg := concat(" ", [
24
26
  "package.json: відсутній scripts.lint-rego — додай",
25
- "\"lint-rego\": \"bun ./npm/scripts/lint-rego.mjs\" (rego.mdc)",
27
+ "\"lint-rego\": \"n-cursor lint-rego\" (rego.mdc)",
26
28
  ])
27
29
  }
28
30
 
@@ -36,13 +36,13 @@ alwaysApply: false
36
36
  bun run lint-rego
37
37
  ```
38
38
 
39
- Скрипт делегує до `bun ./npm/scripts/lint-rego.mjs`. Послідовність:
39
+ Скрипт делегує до CLI `n-cursor lint-rego` (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`). Послідовність:
40
40
 
41
41
  1. **preflight** — наявність `opa` і `regal` у `PATH`; якщо хоча б одного нема — exit 1 з підказкою встановлення;
42
42
  2. `opa check --strict <targets>` — компіляція з типами та `--strict` (мертвий код, неоднозначні правила, незадекларовані змінні);
43
43
  3. `regal lint <targets>` — статичний лінтер Rego ([Styra Regal](https://docs.styra.com/regal)): ловить v0-синтаксис, неявні set-rules і відхилення від `rego.v1`, плюс bugs/idiomatic/style-правила.
44
44
 
45
- Цілі — `npm/policy/` (де живуть Rego-полісі пакета). Інші *.rego поза деревом додай у `LINT_TARGETS` у `lint-rego.mjs`.
45
+ Цілі — `npm/policy/` (де живуть Rego-полісі пакета). Інші *.rego поза деревом додай у `LINT_TARGETS` у `npm/rules/rego/js/lint.mjs`.
46
46
 
47
47
  ### Встановлення інструментів
48
48
 
@@ -58,7 +58,7 @@ bun run lint-rego
58
58
  ```json title="package.json"
59
59
  {
60
60
  "scripts": {
61
- "lint-rego": "bun ./npm/scripts/lint-rego.mjs"
61
+ "lint-rego": "n-cursor lint-rego"
62
62
  }
63
63
  }
64
64
  ```
@@ -25,7 +25,7 @@ import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mj
25
25
  * Альтернатива полю `stylelint` у `package.json` — зовнішній файл конфігу. Якщо
26
26
  * поля немає і файлу немає, фейлимося; якщо є хоч щось — пропускаємо. Поле
27
27
  * `stylelint.extends == "@nitra/stylelint-config"` сам формат — у Rego.
28
- * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер
28
+ * @param {import('../../../../scripts/utils/check-reporter.mjs').CheckReporter} reporter репортер
29
29
  */
30
30
  async function checkStylelintConfigPresence(reporter) {
31
31
  const { pass, fail } = reporter
@@ -9,7 +9,7 @@ alwaysApply: false
9
9
 
10
10
  - **Джерело правил:** перед тим як писати або суттєво змінювати **`.css`**, **`.scss`** або стилі в **`.vue`**, переглянь у корені проєкту (і в релевантних пакетах монорепо, якщо є) поле **`stylelint`** у **`package.json`** (зокрема `extends`), наявні **`.stylelintrc.*`**, **`stylelint.config.*`** та **`.stylelintignore`**. Не покладайся на «типові» правила stylelint з пам’яті — дотримуйся **проєктного** **`@nitra/stylelint-config`** і будь-яких локальних доповнень у репозиторії.
11
11
  - **Форматування** узгоджуй з **`n-text.mdc`** (oxfmt / `.oxfmtrc.json` для css, scss тощо), щоб форматер і stylelint не суперечили один одному.
12
- - **Запуск stylelint:** лише **`npx stylelint`**. Локально — через скрипт **`lint-style`** (`bun run lint-style`); у **GitHub Actions** у кроці **`run`** викликай `npx stylelint '**/*.{css,scss,vue}' --fix` напряму (не через **`bun run lint-style`**). Не використовуй **`bunx stylelint`**. Після змін запускай **`bun run lint-style`** і виправляй усе, що лишилось після auto-fix; за потреби — повний набір `lint-*` (навичка **`n-fix`**).
12
+ - **Запуск stylelint:** лише **`npx stylelint`**. Локально — через скрипт **`lint-style`** (`bun run lint-style`); у **GitHub Actions** у кроці **`run`** викликай `npx stylelint '**/*.{css,scss,vue}' --fix` напряму (не через **`bun run lint-style`**). Не використовуй **`bunx stylelint`**. Після змін запускай **`bun run lint-style`** і виправляй усе, що лишилось після auto-fix; за потреби — повний `bun run lint` (навичка **`/n-lint`**).
13
13
  - **Не розширюй винятки:** не додавай зайві **`stylelint-disable`** без потреби; краще підлаштувати стилі під правила проєкту.
14
14
 
15
15
  **VSCode:** у **`.vscode/extensions.json`** рекомендуй **`stylelint.vscode-stylelint`**. У **`.vscode/settings.json`** вимкни вбудовану валідацію CSS/SCSS/Less і увімкни явні code actions:
@@ -147,36 +147,20 @@ async function checkPackageJsonText(passFn, failFn) {
147
147
  }
148
148
 
149
149
  /**
150
- * Перевіряє скрипт lint-text на коректність v8r-виклику.
151
- * @param {unknown} lintText параметр lintText
150
+ * Перевіряє скрипт lint-text: канонічний `n-cursor lint-text` (CLI пакета `@nitra/cursor` робить
151
+ * `cspell` `runShellcheckText()` `bunx markdownlint-cli2 --fix` → `runV8rWithGlobs()`).
152
+ * Дозволено whitespace навколо команди.
153
+ * @param {unknown} lintText значення `scripts.lint-text` з package.json
152
154
  * @param {(msg: string) => void} passFn callback при успішній перевірці
153
155
  * @param {(msg: string) => void} failFn callback при помилці
154
156
  */
155
157
  function checkLintTextScript(lintText, passFn, failFn) {
156
- const lt = typeof lintText === 'string' ? lintText : ''
157
- const v8rCalls = (lt.match(/bunx v8r/g) || []).length
158
- const quietCalls = (lt.match(/run-v8r?\.mjs/g) || []).length
159
- const eq98Hints = (lt.match(/eq 98/g) || []).length
160
- const legacyV8r = v8rCalls >= 4 && eq98Hints >= 4
161
- const quietBundled = quietCalls === 1
162
- const quietLegacy4x = quietCalls >= 4
163
- const v8rTextOk = legacyV8r || quietBundled || quietLegacy4x
164
- const globsRequired = legacyV8r || quietLegacy4x
165
- const globsOk =
166
- lt.includes('**/*.json') && lt.includes('**/*.yml') && lt.includes('**/*.yaml') && lt.includes('**/*.toml')
167
- const ok =
168
- lt &&
169
- lt.includes('cspell') &&
170
- lt.includes('run-shellcheck-text.mjs') &&
171
- lt.includes('bunx markdownlint-cli2') &&
172
- lt.includes('**/*.mdc') &&
173
- v8rTextOk &&
174
- (!globsRequired || globsOk)
175
- if (ok) {
176
- passFn('package.json: lint-text — shellcheck (run-shellcheck-text.mjs), v8r: run-v8r.mjs або чотири bunx v8r')
158
+ const lt = typeof lintText === 'string' ? lintText.trim() : ''
159
+ if (lt === 'n-cursor lint-text') {
160
+ passFn('lint-text делегує CLI n-cursor lint-text (cspell + shellcheck + markdownlint + v8r)')
177
161
  } else {
178
162
  failFn(
179
- 'package.json: lint-text додай bun ./…/run-shellcheck-text.mjs; v8r: bun ./…/run-v8r.mjs або чотири (bunx v8r "<glob>" || [ $? -eq 98 ]) (див. n-text.mdc)'
163
+ 'package.json: lint-text має бути "n-cursor lint-text" CLI пакета @nitra/cursor виконує cspell shellcheck markdownlint-cli2 v8r (text.mdc)'
180
164
  )
181
165
  }
182
166
  }