@nitra/cursor 1.13.89 → 1.15.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/package.json +1 -1
  3. package/rules/abie/abie.mdc +8 -8
  4. package/rules/abie/js/{applies/check.mjs → applies.mjs} +2 -2
  5. package/rules/abie/js/{env_dns/check.mjs → env_dns.mjs} +3 -3
  6. package/rules/abie/js/{firebase_hosting/check.mjs → firebase_hosting.mjs} +1 -1
  7. package/rules/abie/js/{hc_pairing/check.mjs → hc_pairing.mjs} +4 -4
  8. package/rules/abie/js/{ua_http_route/check.mjs → ua_http_route.mjs} +6 -6
  9. package/rules/abie/js/{ua_node_selector/check.mjs → ua_node_selector.mjs} +5 -5
  10. package/rules/abie/policy/base_deployment_preem/base_deployment_preem.rego +2 -2
  11. package/rules/abie/policy/health_check_policy/health_check_policy.rego +2 -2
  12. package/rules/abie/policy/http_route_base/http_route_base.rego +1 -1
  13. package/rules/abie/utils/enabled.mjs +1 -1
  14. package/rules/abie/utils/k8s-tree.mjs +1 -1
  15. package/rules/adr/js/{hooks/check.mjs → hooks.mjs} +1 -1
  16. package/rules/bun/js/{layout/check.mjs → layout.mjs} +1 -1
  17. package/rules/capacitor/js/{platforms/check.mjs → platforms.mjs} +1 -1
  18. package/rules/changelog/changelog.mdc +1 -1
  19. package/rules/changelog/js/{consistency/check.mjs → consistency.mjs} +2 -2
  20. package/rules/changelog/{js/consistency → utils}/package-manifest.mjs +1 -1
  21. package/rules/docker/js/{lint/check.mjs → lint.mjs} +5 -5
  22. package/rules/docker/lint/lint.mjs +1 -1
  23. package/rules/docker/{js/lint → utils}/docker-hadolint.mjs +1 -1
  24. package/rules/feedback/feedback.mdc +1 -1
  25. package/rules/ga/js/{workflows/check.mjs → workflows.mjs} +5 -5
  26. package/rules/ga/lint/lint.mjs +1 -1
  27. package/rules/graphql/js/{tooling/check.mjs → tooling.mjs} +5 -5
  28. package/rules/hasura/js/{internal_urls/check.mjs → internal_urls.mjs} +4 -4
  29. package/rules/hasura/policy/svc_hl/svc_hl.rego +1 -1
  30. package/rules/image-avif/js/{avif_generation/check.mjs → avif_generation.mjs} +5 -5
  31. package/rules/image-compress/js/{package_setup/check.mjs → package_setup.mjs} +1 -1
  32. package/rules/js-bun-db/js/{safety/check.mjs → safety.mjs} +5 -5
  33. package/rules/js-bun-db/{js/safety → utils}/bun-sql-scan.mjs +1 -1
  34. package/rules/js-bun-redis/js/{imports/check.mjs → imports.mjs} +4 -4
  35. package/rules/js-bun-redis/policy/package_json/package_json.rego +1 -1
  36. package/rules/js-lint/js/{tooling/check.mjs → tooling.mjs} +8 -4
  37. package/rules/js-lint/js-lint.mdc +11 -1
  38. package/rules/js-lint/{js/tooling → utils}/rebuild-oxlint-canonical.mjs +2 -2
  39. package/rules/js-mssql/js/{deps/check.mjs → deps.mjs} +5 -5
  40. package/rules/js-mssql/{js/deps → utils}/mssql-pool-scan.mjs +1 -1
  41. package/rules/js-run/js/{runtime/check.mjs → runtime.mjs} +10 -10
  42. package/rules/js-run/{js/runtime → utils}/bunyan-imports.mjs +1 -1
  43. package/rules/js-run/{js/runtime → utils}/check-env-scan.mjs +1 -1
  44. package/rules/js-run/{js/runtime → utils}/conn-file-rules.mjs +1 -1
  45. package/rules/js-run/{js/runtime → utils}/conn-imports-scan.mjs +1 -1
  46. package/rules/js-run/{js/runtime → utils}/promise-settimeout-scan.mjs +1 -1
  47. package/rules/k8s/js/{manifests/check.mjs → manifests.mjs} +4 -4
  48. package/rules/k8s/k8s.mdc +1 -1
  49. package/rules/nginx-default-tpl/js/{template/check.mjs → template.mjs} +5 -5
  50. package/rules/npm-module/js/{package_structure/check.mjs → package_structure.mjs} +4 -4
  51. package/rules/php/js/{tooling/check.mjs → tooling.mjs} +1 -1
  52. package/rules/rego/js/{applies/check.mjs → applies.mjs} +3 -3
  53. package/rules/rust/auto.md +1 -0
  54. package/rules/rust/fix.mjs +19 -0
  55. package/rules/rust/js/applies.mjs +31 -0
  56. package/rules/rust/policy/lint_rust_yml/lint_rust_yml.rego +55 -0
  57. package/rules/rust/policy/lint_rust_yml/target.json +5 -0
  58. package/rules/rust/policy/lint_rust_yml/template/lint-rust.yml.snippet.yml +44 -0
  59. package/rules/rust/policy/package_json/package_json.rego +18 -0
  60. package/rules/rust/policy/package_json/target.json +5 -0
  61. package/rules/rust/policy/package_json/template/package.json.contains.json +9 -0
  62. package/rules/rust/policy/vscode_extensions/target.json +5 -0
  63. package/rules/rust/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  64. package/rules/rust/policy/vscode_extensions/vscode_extensions.rego +15 -0
  65. package/rules/rust/rust.mdc +27 -0
  66. package/rules/rust/utils/has-cargo-toml.mjs +39 -0
  67. package/rules/security/js/{sample_secret/check.mjs → sample_secret.mjs} +2 -2
  68. package/rules/security/js/{trufflehog/check.mjs → trufflehog.mjs} +3 -3
  69. package/rules/security/security.mdc +2 -2
  70. package/rules/style-lint/js/{tooling/check.mjs → tooling.mjs} +1 -1
  71. package/rules/tauri/js/{tooling/check.mjs → tooling.mjs} +3 -5
  72. package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +9 -11
  73. package/rules/tauri/tauri.mdc +5 -5
  74. package/rules/test/js/{location/check.mjs → location.mjs} +3 -3
  75. package/rules/text/js/{formatting/check.mjs → formatting.mjs} +2 -2
  76. package/rules/vue/js/{packages/check.mjs → packages.mjs} +5 -5
  77. package/scripts/auto-rules.mjs +12 -3
  78. package/scripts/sync-claude-config.mjs +1 -1
  79. package/scripts/utils/discover-checkable-rules.mjs +20 -27
  80. package/scripts/utils/inline-template-links.mjs +1 -1
  81. package/scripts/utils/run-rule.mjs +18 -23
  82. /package/rules/adr/js/{hooks/template → templates/hooks}/.gitignore.snippet +0 -0
  83. /package/rules/docker/{js/lint → utils}/docker-mirror.mjs +0 -0
  84. /package/rules/graphql/{js/tooling → utils}/graphql-gql-scan.mjs +0 -0
  85. /package/rules/js-lint/js/{tooling → data/tooling}/knip-canonical.json +0 -0
  86. /package/rules/js-lint/js/{tooling → data/tooling}/oxlint-canonical-skeleton.json +0 -0
  87. /package/rules/js-lint/js/{tooling → data/tooling}/oxlint-canonical.json +0 -0
  88. /package/rules/js-lint/js/{tooling → data/tooling}/oxlint-rules.tsv +0 -0
  89. /package/rules/k8s/js/{kubescape_exceptions/template → templates/kubescape_exceptions}/.kubescape-exceptions.json.snippet.json +0 -0
  90. /package/rules/security/js/{trufflehog/template → templates/trufflehog}/.trufflehog-exclude.snippet.txt +0 -0
  91. /package/rules/vue/{js/packages → utils}/vue-forbidden-imports.mjs +0 -0
@@ -2,7 +2,7 @@
2
2
  description: Перевірка JavaScript коду
3
3
  globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"
4
4
  alwaysApply: false
5
- version: '1.23'
5
+ version: '1.24'
6
6
  ---
7
7
 
8
8
  **oxlint**, **ESLint**, **jscpd**, **knip**. У скрипті **`lint-js`** і в CI — **`bunx oxlint`**, **`bunx eslint`**, **`bunx jscpd`**, **`bunx knip`** (у CI без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config` мінімум `^3.9.2`** (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint); пакет **`@e18e/eslint-plugin`** окремо не додавай. Пакети oxlint/eslint/jscpd/knip не додавай без потреби монорепо.
@@ -79,6 +79,16 @@ version: '1.23'
79
79
 
80
80
  Але обов'язково перед рефакторингом перевір чи є тести на блоки які підлягають зміні, а саме Bun.test для js та playwright для vue. Якщо є, то перевір чи вони покривають блоки які підлягають зміні. Якщо не покривають або тестів немає — спочатку створи їх, перевір що вони покривають і відпрацьовують коректно, а потім роби рефакторинг і ще раз запускай тести, але якщо тести не відпрацьовують коректно після рефакторингу, то не роби рефакторинг.
81
81
 
82
+ ## Структура спільних модулів: `utils/` vs `lib/`
83
+
84
+ Коли спільні методи виносяться з кількох JS-файлів в окремий каталог — назву каталога обирай за призначенням модулів усередині.
85
+
86
+ - **`utils/`** — низькорівневі, чисті, generic helpers без бізнес-логіки і без залежностей від домену проєкту. Те, що могло б жити в окремому npm-пакеті. Приклади: `formatDate()`, `chunk(array, size)`, `retry(fn, opts)`, `deepMerge()`, `parseDuration('5m')`, `sleep(ms)`.
87
+
88
+ - **`lib/`** — внутрішні модулі / підсистеми проєкту, які знають про домен. Більші за `utils/`, часто з власним state, конфігом або side effects. Приклади: `lib/gmail-client.js`, `lib/circuit-breaker.js`, `lib/skill-loader.js`, `lib/safety/dry-run.js`.
89
+
90
+ Швидкий тест: якщо файл завтра можна опублікувати окремим npm-пакетом без переписування — це `utils/`; якщо він тримає domain-state, читає конфіг проєкту або викликає зовнішні сервіси/файли — це `lib/`. Не плутай із чужими каталогами на кшталт `shared/` чи `common/`: канонічні назви — лише `utils/` і `lib/`.
91
+
82
92
  Додай workflow:
83
93
 
84
94
  ```yaml title=".github/workflows/lint-js.yml"
@@ -2,14 +2,14 @@
2
2
  * Збирає `oxlint-canonical.json` з `oxlint-canonical-skeleton.json` (без поля rules) та списку
3
3
  * правил у `oxlint-rules.tsv` (колонки: ім’я правила, TAB, severity: deny | off | error).
4
4
  *
5
- * Після змін у TSV або скелеті запускай з каталогу пакета: `bun ./rules/js-lint/js/tooling/rebuild-oxlint-canonical.mjs`,
5
+ * Після змін у TSV або скелеті запускай з каталогу пакета: `bun ./rules/js-lint/utils/rebuild-oxlint-canonical.mjs`,
6
6
  * потім скопіюй оновлений канон у корінь споживача як `.oxlintrc.json` за потреби.
7
7
  */
8
8
  import { readFileSync, writeFileSync } from 'node:fs'
9
9
  import { dirname, join } from 'node:path'
10
10
  import { fileURLToPath } from 'node:url'
11
11
 
12
- const dir = dirname(fileURLToPath(import.meta.url))
12
+ const dir = join(dirname(fileURLToPath(import.meta.url)), '..', 'js', 'data', 'tooling')
13
13
  const rules = {}
14
14
  for (const line of readFileSync(join(dir, 'oxlint-rules.tsv'), 'utf8').split('\n')) {
15
15
  const t = line.trim()
@@ -12,8 +12,8 @@ import { existsSync } from 'node:fs'
12
12
  import { readFile } from 'node:fs/promises'
13
13
  import { join, relative } from 'node:path'
14
14
 
15
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
16
- import { findAllPackageJsonPaths } from '../../../../scripts/utils/find-package-json-paths.mjs'
15
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
16
+ import { findAllPackageJsonPaths } from '../../../scripts/utils/find-package-json-paths.mjs'
17
17
  import {
18
18
  findMssqlPerRequestConnectionInText,
19
19
  findSharedMssqlRequestInText,
@@ -22,9 +22,9 @@ import {
22
22
  findUnsafeMssqlInListUnparsedInText,
23
23
  findUnsafeMssqlInListMissingEmptyGuardInText,
24
24
  isMssqlScanSourceFile
25
- } from './mssql-pool-scan.mjs'
26
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
27
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
25
+ } from '../utils/mssql-pool-scan.mjs'
26
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
27
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
28
28
 
29
29
  const VERSION_PREFIX_RE = /^[\^~>=<]+\s*/u
30
30
  const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/u
@@ -30,7 +30,7 @@ import {
30
30
  normalizeSnippet,
31
31
  offsetToLine,
32
32
  walkAstWithAncestors
33
- } from '../../../../scripts/utils/ast-scan-utils.mjs'
33
+ } from '../../../scripts/utils/ast-scan-utils.mjs'
34
34
 
35
35
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/
36
36
  const IN_PLACEHOLDER_END_RE = /\bin\s*\(\s*$/iu
@@ -40,24 +40,24 @@ import {
40
40
  findBunyanImportsInText,
41
41
  isBunyanScanSourceFile,
42
42
  shouldSkipFileForBunyanScan
43
- } from './bunyan-imports.mjs'
44
- import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from './check-env-scan.mjs'
45
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
46
- import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
47
- import { findConnFileRuleViolations, isConnFileRulesSourceFile } from './conn-file-rules.mjs'
43
+ } from '../utils/bunyan-imports.mjs'
44
+ import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from '../utils/check-env-scan.mjs'
45
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
46
+ import { runConftestBatch } from '../../../scripts/utils/run-conftest-batch.mjs'
47
+ import { findConnFileRuleViolations, isConnFileRulesSourceFile } from '../utils/conn-file-rules.mjs'
48
48
  import {
49
49
  findConnFactoryImportsInText,
50
50
  isConnImportsScanSourceFile,
51
51
  isInsideConnDir,
52
52
  resolveConnDirFromPackageJson
53
- } from './conn-imports-scan.mjs'
54
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
53
+ } from '../utils/conn-imports-scan.mjs'
54
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
55
55
  import {
56
56
  findPromiseSetTimeoutInText,
57
57
  isPromiseSetTimeoutScanSourceFile
58
- } from './promise-settimeout-scan.mjs'
59
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
60
- import { getMonorepoPackageRootDirs } from '../../../../scripts/utils/workspaces.mjs'
58
+ } from '../utils/promise-settimeout-scan.mjs'
59
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
60
+ import { getMonorepoPackageRootDirs } from '../../../scripts/utils/workspaces.mjs'
61
61
 
62
62
  /**
63
63
  * Чи існує непорожній за змістом маркер каталогу `src/` (рекомендована структура js-run).
@@ -18,7 +18,7 @@ import {
18
18
  offsetToLine,
19
19
  requireCallModule,
20
20
  walkAstWithAncestors
21
- } from '../../../../scripts/utils/ast-scan-utils.mjs'
21
+ } from '../../../scripts/utils/ast-scan-utils.mjs'
22
22
 
23
23
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
24
24
  const FORBIDDEN_MODULES = new Set(['@nitra/bunyan', 'bunyan'])
@@ -29,7 +29,7 @@
29
29
  * Якщо ключ обчислюваний (`process.env[varName]`) — пропускаємо без помилки,
30
30
  * бо за статичним AST неможливо встановити, яка саме змінна оточення використовується.
31
31
  */
32
- import { offsetToLine, parseProgramOrNull, walkAstWithAncestors } from '../../../../scripts/utils/ast-scan-utils.mjs'
32
+ import { offsetToLine, parseProgramOrNull, walkAstWithAncestors } from '../../../scripts/utils/ast-scan-utils.mjs'
33
33
 
34
34
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
35
35
  const IGNORE_DIRECTIVE_RE = /\/\/\s*@nitra\/cursor\s+ignore-next-line\s+checkEnv\b/u
@@ -14,7 +14,7 @@
14
14
  * Парсимо через oxc-parser; коли файл не парситься — повертаємо порожні результати, щоб
15
15
  * не змішувати помилки синтаксису з порушеннями цього правила.
16
16
  */
17
- import { parseProgramOrNull } from '../../../../scripts/utils/ast-scan-utils.mjs'
17
+ import { parseProgramOrNull } from '../../../scripts/utils/ast-scan-utils.mjs'
18
18
 
19
19
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
20
20
 
@@ -16,7 +16,7 @@
16
16
  * використовується. Якщо файл не парситься — повертаємо порожній результат, спочатку
17
17
  * треба полагодити синтаксис.
18
18
  */
19
- import { langFromPath, normalizeSnippet, offsetToLine } from '../../../../scripts/utils/ast-scan-utils.mjs'
19
+ import { langFromPath, normalizeSnippet, offsetToLine } from '../../../scripts/utils/ast-scan-utils.mjs'
20
20
  import { parseSync } from 'oxc-parser'
21
21
 
22
22
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
@@ -12,7 +12,7 @@
12
12
  * Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках повертається
13
13
  * порожній результат (як інші сканери — спочатку треба полагодити синтаксис).
14
14
  */
15
- import { normalizeSnippet, offsetToLine, parseProgramOrNull } from '../../../../scripts/utils/ast-scan-utils.mjs'
15
+ import { normalizeSnippet, offsetToLine, parseProgramOrNull } from '../../../scripts/utils/ast-scan-utils.mjs'
16
16
 
17
17
  const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/
18
18
 
@@ -140,10 +140,10 @@ import { basename, dirname, join, relative, resolve } from 'node:path'
140
140
 
141
141
  import { isSeq, parseAllDocuments, parseDocument } from 'yaml'
142
142
 
143
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
144
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
145
- import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
146
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
143
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
144
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
145
+ import { runConftestBatch } from '../../../scripts/utils/run-conftest-batch.mjs'
146
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
147
147
 
148
148
  /** Версія набору схем yannh — узгоджено з k8s.mdc */
149
149
  const YAML_LS_MODELINE_RE = /^# yaml-language-server: \$schema=.*\n/
package/rules/k8s/k8s.mdc CHANGED
@@ -39,7 +39,7 @@ alwaysApply: false
39
39
 
40
40
  Якщо в **корені проєкту** є файл **`.kubescape-exceptions.json`** — `lint-k8s` автоматично передає його в `kubescape scan` через **`--exceptions`** ([postureExceptionPolicy](https://github.com/kubescape/kubescape/blob/master/docs/exceptions.md)). Файл — JSON-масив об'єктів з полями `name`, `policyType: "postureExceptionPolicy"`, `actions` (`["alertOnly"]` — знижує fail до alert, не блокує lint), `resources` (resource designator) і `posturePolicies` (масив `controlID`).
41
41
 
42
- Канонічний кейс — **C-0012** (`Applications credentials in configuration files`, High): control тригериться на **імʼя** env, що містить підрядок `secret`/`password`/`key`/`token`, а **не** на значення. Для `HASURA_GRAPHQL_JWT_SECRET` у ConfigMap значення — публічний JWT-конфіг (`jwk_url`, `issuer`), не credentials, але kubescape падає лише через імʼя. Точкове виключення для ConfigMap із цим env — канон: [.kubescape-exceptions.json.snippet.json](./js/kubescape_exceptions/template/.kubescape-exceptions.json.snippet.json)
42
+ Канонічний кейс — **C-0012** (`Applications credentials in configuration files`, High): control тригериться на **імʼя** env, що містить підрядок `secret`/`password`/`key`/`token`, а **не** на значення. Для `HASURA_GRAPHQL_JWT_SECRET` у ConfigMap значення — публічний JWT-конфіг (`jwk_url`, `issuer`), не credentials, але kubescape падає лише через імʼя. Точкове виключення для ConfigMap із цим env — канон: [.kubescape-exceptions.json.snippet.json](./js/templates/kubescape_exceptions/.kubescape-exceptions.json.snippet.json)
43
43
 
44
44
  Підстав свою `attributes.name` (рядок або regex), якщо ConfigMap зветься інакше; виключай контрольно, а не глобально (не додавай винятки без `attributes.name`/`labels`, бо тоді C-0012 знімається для усіх ConfigMap-ів проєкту і реальні витоки credentials теж пройдуть).
45
45
 
@@ -17,11 +17,11 @@ import { existsSync } from 'node:fs'
17
17
  import { readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'
18
18
  import { basename, dirname, join, relative } from 'node:path'
19
19
 
20
- import { findDockerfilePaths } from '../../../docker/js/lint/check.mjs'
21
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
22
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
23
- import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
24
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
20
+ import { findDockerfilePaths } from '../../docker/js/lint.mjs'
21
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
22
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
23
+ import { runConftestBatch } from '../../../scripts/utils/run-conftest-batch.mjs'
24
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
25
25
 
26
26
  const LINE_SPLIT_RE = /\r?\n/u
27
27
  const INI_KEY_RE = /^([A-Za-z_]\w*)\s*=/u
@@ -37,10 +37,10 @@ import {
37
37
  langFromPath,
38
38
  requireCallModule,
39
39
  walkAstWithAncestors
40
- } from '../../../../scripts/utils/ast-scan-utils.mjs'
41
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
42
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
43
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
40
+ } from '../../../scripts/utils/ast-scan-utils.mjs'
41
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
42
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
43
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
44
44
 
45
45
  const execFileAsync = promisify(execFile)
46
46
 
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { existsSync } from 'node:fs'
13
13
 
14
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
14
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
15
15
 
16
16
  /**
17
17
  * Перевіряє відповідність проєкту правилам php.mdc.
@@ -9,9 +9,9 @@
9
9
  * JS тут лишається лише як cross-file гейт: walkDir не виразити декларативно через `target.json`.
10
10
  * Друк короткого pass-повідомлення з контекстом робить `check()` (необовʼязковий).
11
11
  */
12
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
13
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
14
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
12
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
13
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
14
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
15
15
 
16
16
  /**
17
17
  * Чи є хоча б один `.rego`-файл у дереві від `cwd`. Зупиняється на першому матчі.
@@ -0,0 +1 @@
1
+ якщо в проекті є хоч один Cargo.toml
@@ -0,0 +1,19 @@
1
+ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
+
3
+ /**
4
+ * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
6
+ * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
7
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
8
+ */
9
+ export function run(ctx) {
10
+ return runStandardRule(import.meta.dirname, ctx)
11
+ }
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')
17
+ // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
+ process.exit(await runRuleCli(import.meta.dirname))
19
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Applies-гейт правила rust: маркер — наявність `Cargo.toml` у `cwd` або
3
+ * в будь-якому workspace-підкаталозі (рекурсивний пошук з пропуском
4
+ * `node_modules`, `.git`, `.next`, `.turbo`). Якщо повертає `false` —
5
+ * `runStandardRule` пропускає всі концерни (JS і policy) цього правила.
6
+ * `check()` друкує тільки context-pass; реальна робота — у policy-концернах.
7
+ */
8
+ import { existsSync } from 'node:fs'
9
+
10
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
11
+
12
+ import { hasCargoTomlInTree } from '../utils/has-cargo-toml.mjs'
13
+
14
+ const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', '.next', '.turbo'])
15
+
16
+ /**
17
+ * @returns {Promise<boolean>} `true` — правило застосовне; `false` — пропустити
18
+ */
19
+ export function applies() {
20
+ if (existsSync('Cargo.toml')) return Promise.resolve(true)
21
+ return Promise.resolve(hasCargoTomlInTree(process.cwd(), IGNORED_DIR_NAMES))
22
+ }
23
+
24
+ /**
25
+ * @returns {number} exit-код (0 — OK, 1 — порушення)
26
+ */
27
+ export function check() {
28
+ const reporter = createCheckReporter()
29
+ reporter.pass('Знайдено Cargo.toml — застосовуємо правила rust.mdc')
30
+ return reporter.getExitCode()
31
+ }
@@ -0,0 +1,55 @@
1
+ # Перевірка `.github/workflows/lint-rust.yml` для правила rust (rust.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/lint-rust.yml.snippet.yml.
5
+ # Перевіряємо:
6
+ # - кожен `uses` з template (підмножина): actions/checkout@v6,
7
+ # dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2;
8
+ # - кожен `run` з template має бути присутнім (як substring) серед
9
+ # run-кроків input'а — drift-safe: зміна template одразу рухає перевірку.
10
+ # Універсальні workflow-перевірки (name, concurrency, branches) — у `ga.workflow_common`.
11
+ package rust.lint_rust_yml
12
+
13
+ import rego.v1
14
+
15
+ # Усі `uses` з канону workflow.
16
+ expected_uses contains u if {
17
+ some step in data.template.snippet.jobs.lint.steps
18
+ u := object.get(step, "uses", "")
19
+ u != ""
20
+ }
21
+
22
+ # Усі `uses` з input workflow.
23
+ actual_uses contains u if {
24
+ some job in object.get(input, "jobs", {})
25
+ some step in object.get(job, "steps", [])
26
+ u := object.get(step, "uses", "")
27
+ u != ""
28
+ }
29
+
30
+ # Конкатенація всіх `run`-кроків з input workflow.
31
+ all_run_text := concat("\n", [run_text |
32
+ some job in object.get(input, "jobs", {})
33
+ some step in object.get(job, "steps", [])
34
+ run_text := step_run_to_text(step)
35
+ ])
36
+
37
+ deny contains msg if {
38
+ some required_use in expected_uses
39
+ not required_use in actual_uses
40
+ msg := sprintf("lint-rust.yml: відсутній step з `uses: %s` (rust.mdc)", [required_use])
41
+ }
42
+
43
+ deny contains msg if {
44
+ some step in data.template.snippet.jobs.lint.steps
45
+ expected_run := object.get(step, "run", "")
46
+ expected_run != ""
47
+ not contains(all_run_text, expected_run)
48
+ msg := sprintf("lint-rust.yml: жоден крок run не містить %q (rust.mdc)", [expected_run])
49
+ }
50
+
51
+ step_run_to_text(step) := step.run if is_string(step.run)
52
+
53
+ else := concat("\n", [s | some s in step.run]) if is_array(step.run)
54
+
55
+ else := ""
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-rust.yml", "required": true },
4
+ "missingMessage": ".github/workflows/lint-rust.yml не існує — створи з канонічним вмістом (rust.mdc)"
5
+ }
@@ -0,0 +1,44 @@
1
+ name: Lint Rust
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev
7
+ - main
8
+ paths:
9
+ - '**/*.rs'
10
+ - '**/Cargo.toml'
11
+ - '**/Cargo.lock'
12
+ - '**/rustfmt.toml'
13
+ - '**/clippy.toml'
14
+
15
+ pull_request:
16
+ branches:
17
+ - dev
18
+ - main
19
+
20
+ concurrency:
21
+ group: ${{ github.ref }}-${{ github.workflow }}
22
+ cancel-in-progress: true
23
+
24
+ jobs:
25
+ lint:
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ contents: read
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+ with:
32
+ persist-credentials: false
33
+
34
+ - uses: dtolnay/rust-toolchain@stable
35
+ with:
36
+ components: rustfmt, clippy
37
+
38
+ - uses: Swatinem/rust-cache@v2
39
+
40
+ - name: Rustfmt
41
+ run: cargo fmt --all -- --check
42
+
43
+ - name: Clippy
44
+ run: cargo clippy --all-targets --all-features -- -D warnings
@@ -0,0 +1,18 @@
1
+ # Перевірка `package.json` для правила rust (rust.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "contains": ... } }
4
+ # Структура --data сформована з template/package.json.contains.json.
5
+ # Перевіряємо substring-вимоги до scripts.lint-rust: усі три кроки
6
+ # (`cargo fmt`, `cargo clippy --fix`, фінальний `cargo clippy ... -D warnings`)
7
+ # мають бути присутніми у значенні скрипта.
8
+ package rust.package_json
9
+
10
+ import rego.v1
11
+
12
+ deny contains msg if {
13
+ some script_name, needles in data.template.contains.scripts
14
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
15
+ some needle in needles
16
+ not contains(actual, needle)
17
+ msg := sprintf("package.json: scripts.%s має містити %q (rust.mdc)", [script_name, needle])
18
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json", "required": true },
4
+ "missingMessage": "package.json не існує — створи зі scripts.lint-rust (rust.mdc)"
5
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "scripts": {
3
+ "lint-rust": [
4
+ "cargo fmt --all",
5
+ "cargo clippy --fix --allow-staged --allow-dirty",
6
+ "cargo clippy --all-targets --all-features -- -D warnings"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/extensions.json", "required": true },
4
+ "missingMessage": ".vscode/extensions.json не існує — створи з recommendations \"rust-lang.rust-analyzer\" і \"tamasfe.even-better-toml\" (rust.mdc)"
5
+ }
@@ -0,0 +1 @@
1
+ { "recommendations": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"] }
@@ -0,0 +1,15 @@
1
+ # Перевірка `.vscode/extensions.json` для правила rust (rust.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/extensions.json.snippet.json.
5
+ # Semantics: subset-of для recommendations — кожен з канону має бути присутнім,
6
+ # інші екстеншени дозволені.
7
+ package rust.vscode_extensions
8
+
9
+ import rego.v1
10
+
11
+ deny contains msg if {
12
+ some rec in data.template.snippet.recommendations
13
+ not rec in {r | some r in object.get(input, "recommendations", [])}
14
+ msg := sprintf(".vscode/extensions.json: recommendations має містити %q (rust.mdc)", [rec])
15
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Перевірка Rust коду
3
+ globs: "**/{Cargo.toml,Cargo.lock,rustfmt.toml,clippy.toml,.vscode/extensions.json,package.json},**/*.rs"
4
+ alwaysApply: false
5
+ version: '1.0'
6
+ ---
7
+
8
+ **rustfmt** ([rust-lang/rustfmt](https://github.com/rust-lang/rustfmt)) — форматер; **clippy** ([rust-lang/rust-clippy](https://github.com/rust-lang/rust-clippy)) — лінтер. У скрипті **`lint-rust`** локально йдуть три кроки в одному рядку: `cargo fmt --all` → `cargo clippy --fix --allow-staged --allow-dirty --all-targets --all-features` → фінальний `cargo clippy --all-targets --all-features -- -D warnings`. У CI — без `--fix`: `cargo fmt --all -- --check` і `cargo clippy ... -- -D warnings` (див. `lint-rust.yml`).
9
+
10
+ `cargo`, `rustfmt`, `clippy` не додавай у `devDependencies` — це Rust toolchain, ставиться через `rustup` локально або через `dtolnay/rust-toolchain@stable` у CI.
11
+
12
+ Канон `scripts.lint-rust` (substring requirement): [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
13
+
14
+ У `.vscode/extensions.json` `recommendations` мають містити `rust-lang.rust-analyzer` і `tamasfe.even-better-toml`: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
15
+
16
+ Канон workflow `.github/workflows/lint-rust.yml`: [lint-rust.yml.snippet.yml](./policy/lint_rust_yml/template/lint-rust.yml.snippet.yml)
17
+
18
+ Перед **`./.github/actions/setup-bun-deps`** в інших workflows йде **`actions/checkout@v6`** (див. **ga.mdc**). У **lint-rust.yml** після checkout — `dtolnay/rust-toolchain@stable` (з `components: rustfmt, clippy`) + `Swatinem/rust-cache@v2` для кешу `~/.cargo` і `target/`. Bun composite дії тут не потрібен — toolchain ставиться напряму.
19
+
20
+ ## Композиція з Tauri
21
+
22
+ Tauri-проєкт завжди має `src-tauri/Cargo.toml`, тому правило `rust` активується автоматично разом з `tauri`. Поділ обов'язків:
23
+
24
+ - `rust` — `lint-rust` скрипт, `rust-analyzer`, `even-better-toml`, CI workflow.
25
+ - `tauri` — `tauri-apps.tauri-vscode` (див. **tauri.mdc**).
26
+
27
+ Обидва правила перевіряють `.vscode/extensions.json` за `contains`-семантикою; конкурентного запису немає.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Rule-level applies-walker правила rust: рекурсивно шукає Cargo.toml у
3
+ * cwd або будь-якому workspace-підкаталозі. Пропускає `node_modules`, `.git`,
4
+ * `.next`, `.turbo` за тим самим списком, що `npm/scripts/auto-rules.mjs`.
5
+ *
6
+ * Утиліта rule-local, бо лише `rust` потребує "знайти Cargo.toml у дереві";
7
+ * якщо з'явиться другий споживач — підняти у `npm/scripts/utils/`.
8
+ */
9
+ import { readdirSync } from 'node:fs'
10
+ import { join } from 'node:path'
11
+
12
+ /**
13
+ * Чи присутній хоч один Cargo.toml у дереві `root` (синхронно, з раннім return).
14
+ * @param {string} root абсолютний шлях кореня
15
+ * @param {Set<string>} ignoredDirNames імена директорій, в які НЕ заходимо
16
+ * @returns {boolean} true, якщо знайдено Cargo.toml
17
+ */
18
+ export function hasCargoTomlInTree(root, ignoredDirNames) {
19
+ /**
20
+ * @param {string} dir абсолютний шлях каталогу для обходу
21
+ * @returns {boolean} true якщо в піддереві є Cargo.toml
22
+ */
23
+ function walk(dir) {
24
+ let entries
25
+ try {
26
+ entries = readdirSync(dir, { withFileTypes: true })
27
+ } catch {
28
+ return false
29
+ }
30
+ for (const entry of entries) {
31
+ if (entry.isFile() && entry.name === 'Cargo.toml') return true
32
+ if (entry.isDirectory() && !ignoredDirNames.has(entry.name)) {
33
+ if (walk(join(dir, entry.name))) return true
34
+ }
35
+ }
36
+ return false
37
+ }
38
+ return walk(root)
39
+ }
@@ -29,8 +29,8 @@
29
29
  import { readFile } from 'node:fs/promises'
30
30
  import { relative, sep } from 'node:path'
31
31
 
32
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
33
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
32
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
33
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
34
34
 
35
35
  /** Суфікс basename'а прикладного файлу (`config.example`, `.env.dist`). */
36
36
  const EXAMPLE_SUFFIX_RE = /\.(?:example|sample|template|dist)$/iu
@@ -11,11 +11,11 @@ import { readFile } from 'node:fs/promises'
11
11
  import { dirname, join } from 'node:path'
12
12
  import { fileURLToPath } from 'node:url'
13
13
 
14
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
15
- import { checkTextSubset } from '../../../../scripts/utils/template.mjs'
14
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
15
+ import { checkTextSubset } from '../../../scripts/utils/template.mjs'
16
16
 
17
17
  const HERE = dirname(fileURLToPath(import.meta.url))
18
- const SNIPPET_PATH = join(HERE, 'template', '.trufflehog-exclude.snippet.txt')
18
+ const SNIPPET_PATH = join(HERE, 'templates', 'trufflehog', '.trufflehog-exclude.snippet.txt')
19
19
 
20
20
  /**
21
21
  * @returns {Promise<number>} exit-код перевірки
@@ -24,7 +24,7 @@ version: '2.1'
24
24
 
25
25
  ## `.trufflehog-exclude` (рекомендована основа)
26
26
 
27
- Канон (допускає розширення): [.trufflehog-exclude.snippet.txt](./js/trufflehog/template/.trufflehog-exclude.snippet.txt)
27
+ Канон (допускає розширення): [.trufflehog-exclude.snippet.txt](./js/templates/trufflehog/.trufflehog-exclude.snippet.txt)
28
28
 
29
29
  **Важливо:** один regex-pattern на рядок, без TOML-обгортки; коментарі починаються з `#`.
30
30
 
@@ -45,7 +45,7 @@ Workflow обовʼязковий — забезпечує незалежний
45
45
  - Правильно: `DB_PASSWORD=sample-secret`, `password: "sample-secret"`
46
46
  - Неправильно: `DB_PASSWORD=secret`, `password: "secret"`
47
47
 
48
- Перевіряється лише `secret` у позиції значення (після `=`, `:`, `=>`); імена ключів на кшталт `client_secret` не чіпаються. Concern `security.sample_secret` — деталі скану в `js/sample_secret/check.mjs`.
48
+ Перевіряється лише `secret` у позиції значення (після `=`, `:`, `=>`); імена ключів на кшталт `client_secret` не чіпаються. Concern `security.sample_secret` — деталі скану в `js/sample_secret.mjs`.
49
49
 
50
50
  ## Перевірка
51
51
 
@@ -19,7 +19,7 @@
19
19
  import { existsSync } from 'node:fs'
20
20
  import { readFile } from 'node:fs/promises'
21
21
 
22
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
22
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
23
23
 
24
24
  /**
25
25
  * Альтернатива полю `stylelint` у `package.json` — зовнішній файл конфігу. Якщо