@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
@@ -18,8 +18,8 @@
18
18
  import { existsSync, statSync } from 'node:fs'
19
19
  import { readFile } from 'node:fs/promises'
20
20
 
21
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
22
- import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
21
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
22
+ import { runConftestBatch } from '../../../scripts/utils/run-conftest-batch.mjs'
23
23
 
24
24
  /**
25
25
  * Чи є префікс `@tauri-apps/` у ключах `dependencies` або `devDependencies`.
@@ -70,9 +70,7 @@ export async function check() {
70
70
 
71
71
  const extPath = '.vscode/extensions.json'
72
72
  if (!existsSync(extPath)) {
73
- fail(
74
- `${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" і "rust-lang.rust-analyzer" (tauri.mdc)`
75
- )
73
+ fail(`${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" (tauri.mdc)`)
76
74
  return reporter.getExitCode()
77
75
  }
78
76
  const violations = runConftestBatch({
@@ -1,24 +1,22 @@
1
1
  # Перевірка `.vscode/extensions.json` для tauri (tauri.mdc).
2
2
  #
3
- # Викликається з `rules/tauri/fix.mjs` через `runConftestBatch` лише ПІСЛЯ того,
4
- # як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
5
- # у будь-якому пакеті, або залежність `@tauri-apps/*`). Без `target.json` поруч
6
- # (не auto-discoverable через `n-cursor fix`) — інакше false-positive порушення на не-Tauri проєктах.
3
+ # Викликається з `rules/tauri/js/tooling.mjs` через `runConftestBatch` лише
4
+ # ПІСЛЯ того, як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог,
5
+ # `tauri.conf.json` у будь-якому пакеті, або залежність `@tauri-apps/*`).
6
+ # Без `target.json` поруч (не auto-discoverable через `n-cursor fix`) — це
7
+ # conditional правило.
7
8
  #
8
- # Canonical (tauri.mdc): `recommendations` має містити обидва записи —
9
- # - tauri-apps.tauri-vscode
10
- # - rust-lang.rust-analyzer
11
- #
12
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
9
+ # Canonical (tauri.mdc): `recommendations` має містити `tauri-apps.tauri-vscode`.
10
+ # `rust-lang.rust-analyzer` і `tamasfe.even-better-toml` — вимагаються правилом
11
+ # `rust` (rust.mdc), бо Tauri-проєкт завжди має `src-tauri/Cargo.toml`.
13
12
  package tauri.vscode_extensions
14
13
 
15
14
  import rego.v1
16
15
 
17
- required_extensions := {"tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"}
16
+ required_extensions := {"tauri-apps.tauri-vscode"}
18
17
 
19
18
  missing_extension_template := ".vscode/extensions.json: recommendations має містити %q (tauri.mdc)"
20
19
 
21
- # Множина усіх записів `recommendations` (поза deny — performance/non-loop-expression).
22
20
  recommendations_set := {r | some r in object.get(input, "recommendations", [])}
23
21
 
24
22
  deny contains msg if {
@@ -2,15 +2,15 @@
2
2
  description: Tauri
3
3
  globs: "**/src-tauri/**,**/tauri.conf.json"
4
4
  alwaysApply: false
5
- version: '1.1'
5
+ version: '1.2'
6
6
  ---
7
7
 
8
-
9
- в файлі .vscode/extensions.json є налаштування для Vue:
8
+ У `.vscode/extensions.json` `recommendations` має містити `tauri-apps.tauri-vscode`:
10
9
 
11
10
  ```json title=".vscode/extensions.json"
12
11
  {
13
- "recommendations": ["tauri-apps.tauri-vscode",
14
- "rust-lang.rust-analyzer"]
12
+ "recommendations": ["tauri-apps.tauri-vscode"]
15
13
  }
16
14
  ```
15
+
16
+ Розширені Rust-вимоги (`rust-lang.rust-analyzer`, `tamasfe.even-better-toml`, скрипт `lint-rust`, CI `lint-rust.yml`) — у правилі **`rust`** (`n-rust.mdc`). Tauri-проєкт завжди має `src-tauri/Cargo.toml`, тому `rust` активується автоматично разом з `tauri`.
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { basename, dirname, relative } from 'node:path'
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
  const TESTS_DIR_NAME = 'tests'
17
17
 
@@ -32,8 +32,8 @@
32
32
  import { existsSync } from 'node:fs'
33
33
  import { readFile } from 'node:fs/promises'
34
34
 
35
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
36
- import { anyRunStepIncludes, parseWorkflowYaml } from '../../../../scripts/utils/gha-workflow.mjs'
35
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
36
+ import { anyRunStepIncludes, parseWorkflowYaml } from '../../../scripts/utils/gha-workflow.mjs'
37
37
 
38
38
  /** Заголовок абзацу про апостроф у text.mdc / n-text.mdc. */
39
39
  const UK_APOSTROPHE_HEADING = '**Український апостроф:**'
@@ -25,16 +25,16 @@ import { existsSync } from 'node:fs'
25
25
  import { readFile } from 'node:fs/promises'
26
26
  import { join, relative } from 'node:path'
27
27
 
28
- import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
28
+ import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
29
29
  import {
30
30
  findForbiddenNodeImportsInVueFile,
31
31
  findForbiddenVueImportsInSourceFile,
32
32
  isVueImportScanSourceFile,
33
33
  shouldSkipFileForVueImportScan
34
- } from './vue-forbidden-imports.mjs'
35
- import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
36
- import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
37
- import { getMonorepoPackageRootDirs } from '../../../../scripts/utils/workspaces.mjs'
34
+ } from '../utils/vue-forbidden-imports.mjs'
35
+ import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
36
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
37
+ import { getMonorepoPackageRootDirs } from '../../../scripts/utils/workspaces.mjs'
38
38
 
39
39
  const ESBUILD_RE = /\besbuild\b/
40
40
 
@@ -16,13 +16,13 @@ import { existsSync } from 'node:fs'
16
16
  import { readdir, readFile } from 'node:fs/promises'
17
17
  import { basename, join, relative } from 'node:path'
18
18
 
19
- import { textHasBunSqlImport } from '../rules/js-bun-db/js/safety/bun-sql-scan.mjs'
19
+ import { textHasBunSqlImport } from '../rules/js-bun-db/utils/bun-sql-scan.mjs'
20
20
  import {
21
21
  isGqlScanSourceFile,
22
22
  shouldSkipFileForGqlScan,
23
23
  sourceFileHasGqlTaggedTemplate
24
- } from '../rules/graphql/js/tooling/graphql-gql-scan.mjs'
25
- import { contentForVueImportScan } from '../rules/vue/js/packages/vue-forbidden-imports.mjs'
24
+ } from '../rules/graphql/utils/graphql-gql-scan.mjs'
25
+ import { contentForVueImportScan } from '../rules/vue/utils/vue-forbidden-imports.mjs'
26
26
 
27
27
  /** Порядок автододавання правил відповідно до `rules/<rule>/auto.md`. */
28
28
  export const AUTO_RULE_ORDER = Object.freeze([
@@ -48,6 +48,7 @@ export const AUTO_RULE_ORDER = Object.freeze([
48
48
  'npm-module',
49
49
  'php',
50
50
  'rego',
51
+ 'rust',
51
52
  'security',
52
53
  'style-lint',
53
54
  'text',
@@ -291,6 +292,7 @@ function updateDirFacts(dirName, facts) {
291
292
  * @param {string} relPath шлях відносно кореня
292
293
  * @param {{
293
294
  * hasCapacitorConfig: boolean,
295
+ * hasCargoToml: boolean,
294
296
  * hasDockerfile: boolean,
295
297
  * hasJsLikeSource: boolean,
296
298
  * hasNginxDefaultTplFile: boolean,
@@ -304,6 +306,9 @@ function updateFileFacts(fileName, relPath, facts) {
304
306
  if (fileName === 'capacitor.config.json') {
305
307
  facts.hasCapacitorConfig = true
306
308
  }
309
+ if (fileName === 'Cargo.toml') {
310
+ facts.hasCargoToml = true
311
+ }
307
312
  if (fileName === 'Dockerfile' || fileName.startsWith('Dockerfile.')) {
308
313
  facts.hasDockerfile = true
309
314
  }
@@ -407,6 +412,7 @@ async function updateHasuraFactFromFile(absPath, fileName, facts) {
407
412
  * @param {{
408
413
  * hasBunSqlImport: boolean,
409
414
  * hasCapacitorConfig: boolean,
415
+ * hasCargoToml: boolean,
410
416
  * hasDockerfile: boolean,
411
417
  * hasGqlTaggedTemplates: boolean,
412
418
  * hasHasuraConfig: boolean,
@@ -493,6 +499,7 @@ export function isMonorepoPackage(packageJson) {
493
499
  * @param {string} root абсолютний шлях кореня репозиторію
494
500
  * @returns {Promise<{
495
501
  * hasCapacitorConfig: boolean,
502
+ * hasCargoToml: boolean,
496
503
  * hasDockerfile: boolean,
497
504
  * hasGaWorkflowsDir: boolean,
498
505
  * hasBunSqlImport: boolean,
@@ -511,6 +518,7 @@ export async function collectAutoRuleFacts(root) {
511
518
  const facts = {
512
519
  hasBunSqlImport: false,
513
520
  hasCapacitorConfig: false,
521
+ hasCargoToml: false,
514
522
  hasDockerfile: false,
515
523
  hasGaWorkflowsDir: existsSync(join(root, '.github', 'workflows')),
516
524
  hasGqlTaggedTemplates: false,
@@ -656,6 +664,7 @@ export async function detectAutoRules({
656
664
  { enabled: npmDirExists, id: 'npm-module' },
657
665
  { enabled: composerJsonExists, id: 'php' },
658
666
  { enabled: facts.hasRegoFile, id: 'rego' },
667
+ { enabled: facts.hasCargoToml, id: 'rust' },
659
668
  { enabled: facts.hasVueOrCssSource, id: 'style-lint' }
660
669
  ]
661
670
  for (const item of autoRuleChecks) {
@@ -57,7 +57,7 @@ const ADR_HOOK_SCRIPT_NAME = 'capture-decisions.sh'
57
57
  const ADR_NORMALIZE_HOOK_SCRIPT_NAME = 'normalize-decisions.sh'
58
58
  const TEMPLATE_DIR_NAME = '.claude-template'
59
59
  /** Відносний шлях до канонічного фрагмента `.gitignore` для ADR Stop-hook'ів у tarball пакета. */
60
- export const ADR_GITIGNORE_SNIPPET_REL = 'rules/adr/js/hooks/template/.gitignore.snippet'
60
+ export const ADR_GITIGNORE_SNIPPET_REL = 'rules/adr/js/templates/hooks/.gitignore.snippet'
61
61
  const GITIGNORE_FILE = '.gitignore'
62
62
  const EOL_RE = /\r?\n/u
63
63
 
@@ -1,29 +1,26 @@
1
1
  /**
2
- * Discovery rules для CLI `check`. Шукає правила, для яких є щось «прогонне»:
3
- * - JS concerns: `rules/<id>/js/<concern>/<check.mjs | check-*.mjs>` кожен concern окремий вузол.
2
+ * Discovery rules для CLI `fix`. Шукає правила, для яких є щось «прогонне»:
3
+ * - JS concerns: `rules/<id>/js/<concern>.mjs` один файл = один concern.
4
4
  * - Policy concerns: `rules/<id>/policy/<concern>/target.json` — пара з `<concern>.rego`.
5
5
  *
6
- * Каталог `js/utils/` свідомо пропускається — це хелпери, не концерни.
7
- * Файли `*.test.mjs` фільтруються regex (`^check(?:-.+)?\.mjs$`).
6
+ * Файли з префіксом `_` (зокрема каталог `_lib/`) і `*.test.mjs` пропускаються — це хелпери й тести.
8
7
  *
9
8
  * Намеренно НЕ парсимо `target.json` тут (це робить runner). Discovery — швидкий скан структури:
10
9
  * шляхи + назви, без I/O вмісту.
11
10
  *
12
- * Історичний контекст: convention пройшла еволюцію `js/` (до 1.11.12) → `fix/` (1.11.10–1.13.79)
13
- * → знову `js/` (1.13.80+, після появи кореневого `fix.mjs` як entry-point правила, щоб не плутати
14
- * з папкою `fix/`). Convention тепер за технологією: `js/` ↔ `policy/`.
11
+ * Історичний контекст: convention пройшла еволюцію
12
+ * `js/<concern>/check.mjs` (1.13.80–1.13.89)
13
+ * `js/<concern>.mjs` (1.13.90+, flat: концерн = файл, не каталог)
14
+ * Helpers, tests, templates і data винесені в окремі топ-level папки правила (`js/_lib/`,
15
+ * `tests/`, `templates/`, `data/`).
15
16
  */
16
17
  import { existsSync } from 'node:fs'
17
18
  import { readdir } from 'node:fs/promises'
18
19
  import { join } from 'node:path'
19
20
 
20
- const CHECK_FILENAME_RE = /^check(?:-.+)?\.mjs$/u
21
- const TEST_SUFFIX = '.test.mjs'
22
-
23
21
  /**
24
22
  * @typedef {object} JsConcern
25
- * @property {string} name імʼя концерну (`<name>` у `js/<name>/`)
26
- * @property {string[]} files імена `check*.mjs` у концерні (відсортовані алфавітно)
23
+ * @property {string} name імʼя концерну (= basename файла `js/<name>.mjs` без розширення)
27
24
  */
28
25
 
29
26
  /**
@@ -39,30 +36,26 @@ const TEST_SUFFIX = '.test.mjs'
39
36
  */
40
37
 
41
38
  /**
42
- * Перелічує JS-концерни одного правила: підкаталоги `js/<name>/` з принаймні одним `check*.mjs`.
39
+ * Перелічує JS-концерни одного правила: файли `js/<name>.mjs` (один файл один concern).
43
40
  *
44
- * `js/utils/` свідомо пропускається це хелпери, а не концерни.
41
+ * Файли з префіксом `_` (наприклад каталог `_lib/`) і `*.test.mjs` пропускаються.
45
42
  * @param {string} jsDir абсолютний шлях `rules/<id>/js/`
46
43
  * @returns {Promise<JsConcern[]>} концерни в алфавітному порядку
47
44
  */
48
45
  async function listJsConcerns(jsDir) {
49
46
  if (!existsSync(jsDir)) return []
50
- const topLevel = await readdir(jsDir, { withFileTypes: true })
51
-
47
+ const entries = await readdir(jsDir, { withFileTypes: true })
52
48
  /** @type {JsConcern[]} */
53
49
  const concerns = []
54
- for (const entry of topLevel) {
55
- if (!entry.isDirectory() || entry.name === 'utils' || entry.name.startsWith('.')) continue
56
- const concernDir = join(jsDir, entry.name)
57
- const dirContents = await readdir(concernDir)
58
- const files = dirContents
59
- .filter(n => CHECK_FILENAME_RE.test(n) && !n.endsWith(TEST_SUFFIX))
60
- .toSorted((a, b) => a.localeCompare(b))
61
- if (files.length > 0) {
62
- concerns.push({ name: entry.name, files })
63
- }
50
+ for (const entry of entries) {
51
+ if (!entry.isFile()) continue
52
+ if (!entry.name.endsWith('.mjs')) continue
53
+ if (entry.name.endsWith('.test.mjs')) continue
54
+ if (entry.name.startsWith('_')) continue
55
+ if (entry.name.startsWith('.')) continue
56
+ const name = entry.name.slice(0, -'.mjs'.length)
57
+ concerns.push({ name })
64
58
  }
65
-
66
59
  return concerns.toSorted((a, b) => a.name.localeCompare(b.name))
67
60
  }
68
61
 
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises'
3
3
  import { basename, extname, join } from 'node:path'
4
4
 
5
5
  const MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
6
- const TEMPLATE_SEGMENT_RE = /\/template\//
6
+ const TEMPLATE_SEGMENT_RE = /\/templates?\//
7
7
  /** Статичні regexp-літерали `^(.+)\.<slot>\.<ext>$` — без `RegExp(variable)`. */
8
8
  const SLOT_SUFFIX_RES = [/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
9
9
 
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Оркестратор одного правила під CLI `check`.
2
+ * Оркестратор одного правила під CLI `fix`.
3
3
  *
4
4
  * Послідовність (concerns у межах правила — алфавітно):
5
- * 1. **applies-гейт** з `js/applies/check.mjs`. Якщо модуль експортує `applies()` і вона повертає
5
+ * 1. **applies-гейт** з `js/applies.mjs`. Якщо модуль експортує `applies()` і вона повертає
6
6
  * false — друкуємо `✅ правило не застосовне` і завершуємо без подальших викликів.
7
- * 2. **JS-концерни** — кожен `check*.mjs` у `js/<concern>/`. Concern `applies` теж може мати
7
+ * 2. **JS-концерни** — кожен файл `js/<concern>.mjs`. Concern `applies` теж може мати
8
8
  * `check()` для друку контексту (його `applies()` уже відпрацював на кроці 1, він не повторюється).
9
9
  * 3. **Policy-концерни** — кожен `policy/<concern>/target.json` через `runConftestBatch`.
10
10
  * Резолвер `resolveTargetFiles` ділить cache (`walkCache`) між концернами.
@@ -24,32 +24,29 @@ import { resolveConcernTemplateData } from './template.mjs'
24
24
  const APPLIES_CONCERN_NAME = 'applies'
25
25
 
26
26
  /**
27
- * Обчислює абсолютний шлях до файла-check у JS-концерні: `rules/<id>/js/<concern>/<file>`.
28
- * Convention `js/` (за технологією) повернулась у 1.13.80 після короткої епохи `fix/`
29
- * (1.11.10–1.13.79) — щоб не плутати з кореневим `fix.mjs` entry-point'ом.
27
+ * Обчислює абсолютний шлях до файла-концерну: `rules/<id>/js/<concern>.mjs`.
28
+ * Flat-convention з 1.14.0 концерн = файл, не каталог.
30
29
  * @param {string} bundledRulesDir абсолютний `rules/`
31
30
  * @param {string} ruleId id правила
32
31
  * @param {import('./discover-checkable-rules.mjs').JsConcern} concern опис концерну
33
- * @param {string} fileName імʼя файла з `concern.files`
34
32
  * @returns {string} абсолютний шлях
35
33
  */
36
- function resolveJsCheckPath(bundledRulesDir, ruleId, concern, fileName) {
37
- return join(bundledRulesDir, ruleId, 'js', concern.name, fileName)
34
+ function resolveJsCheckPath(bundledRulesDir, ruleId, concern) {
35
+ return join(bundledRulesDir, ruleId, 'js', `${concern.name}.mjs`)
38
36
  }
39
37
 
40
38
  /**
41
- * Спробувати викликати applies() гейт з `js/applies/check.mjs` правила.
42
- * Гейт активний лише за наявності концерну з імʼям `applies` і експортом-функцією `applies` у його
43
- * першому check-файлі (алфавіт).
39
+ * Спробувати викликати applies() гейт з `js/applies.mjs` правила.
40
+ * Гейт активний лише за наявності концерну з імʼям `applies` і експортом-функцією `applies`.
44
41
  * @param {string} bundledRulesDir абсолютний `rules/`
45
42
  * @param {import('./discover-checkable-rules.mjs').CheckableRule} rule опис правила
46
43
  * @returns {Promise<boolean>} `true` — правило застосовне (або гейту немає); `false` — пропустити
47
44
  */
48
45
  async function evaluateAppliesGate(bundledRulesDir, rule) {
49
46
  const concern = rule.jsConcerns.find(c => c.name === APPLIES_CONCERN_NAME)
50
- if (!concern || concern.files.length === 0) return true
51
- const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern, concern.files[0])
52
- // eslint-disable-next-line no-unsanitized/method -- path будується з discovered concern/file, які пройшли regex CHECK_FILENAME_RE
47
+ if (!concern) return true
48
+ const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern)
49
+ // eslint-disable-next-line no-unsanitized/method -- path з discovered concern, файл з whitelist'у readdir
53
50
  const mod = await import(path)
54
51
  if (typeof mod.applies !== 'function') return true
55
52
  return Boolean(await mod.applies())
@@ -116,14 +113,12 @@ export async function runRule(rule, bundledRulesDir, walkCache) {
116
113
  let totalCode = 0
117
114
 
118
115
  for (const concern of rule.jsConcerns) {
119
- for (const fileName of concern.files) {
120
- const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern, fileName)
121
- // eslint-disable-next-line no-unsanitized/method -- path будується з discovered concern/file, які пройшли regex CHECK_FILENAME_RE
122
- const mod = await import(path)
123
- if (typeof mod.check === 'function') {
124
- const code = await mod.check()
125
- if (code !== 0) totalCode = 1
126
- }
116
+ const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern)
117
+ // eslint-disable-next-line no-unsanitized/method -- path з discovered concern, файл з whitelist'у readdir
118
+ const mod = await import(path)
119
+ if (typeof mod.check === 'function') {
120
+ const code = await mod.check()
121
+ if (code !== 0) totalCode = 1
127
122
  }
128
123
  }
129
124