@nitra/cursor 1.13.15 → 1.13.26

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 (77) hide show
  1. package/.claude-template/hooks/normalize-decisions.sh +1 -1
  2. package/CHANGELOG.md +85 -0
  3. package/package.json +1 -1
  4. package/rules/abie/abie.mdc +1 -4
  5. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
  6. package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
  7. package/rules/adr/adr.mdc +2 -2
  8. package/rules/docker/docker.mdc +2 -50
  9. package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
  10. package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
  11. package/rules/docker/policy/package_json/package_json.rego +11 -28
  12. package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
  13. package/rules/image-avif/policy/package_json/package_json.rego +21 -35
  14. package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
  15. package/rules/image-compress/image-compress.mdc +2 -8
  16. package/rules/image-compress/policy/package_json/package_json.rego +24 -65
  17. package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
  18. package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
  19. package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
  20. package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
  21. package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
  22. package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
  23. package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
  24. package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
  25. package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
  26. package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
  27. package/rules/js-lint/policy/package_json/package_json.rego +22 -42
  28. package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
  29. package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  30. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
  31. package/rules/js-run/policy/configmap/configmap.rego +11 -35
  32. package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
  33. package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
  34. package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
  35. package/rules/js-run/policy/package_json/package_json.rego +10 -21
  36. package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
  37. package/rules/php/php.mdc +2 -56
  38. package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
  39. package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
  40. package/rules/php/policy/package_json/package_json.rego +9 -12
  41. package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
  42. package/rules/security/fix/trufflehog/check.mjs +45 -0
  43. package/rules/security/fix/trufflehog/template/.trufflehog-exclude.snippet.txt +6 -0
  44. package/rules/security/policy/lint_security_yml/lint_security_yml.rego +33 -0
  45. package/rules/security/policy/lint_security_yml/target.json +8 -0
  46. package/rules/security/policy/lint_security_yml/template/lint-security.yml.snippet.yml +31 -0
  47. package/rules/security/policy/package_json/template/package.json.deny.json +2 -2
  48. package/rules/security/policy/package_json/template/package.json.snippet.json +1 -1
  49. package/rules/security/security.mdc +17 -38
  50. package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
  51. package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
  52. package/rules/style-lint/policy/package_json/package_json.rego +22 -30
  53. package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
  54. package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
  55. package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  56. package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
  57. package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
  58. package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
  59. package/rules/text/policy/cspell/cspell.rego +39 -59
  60. package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
  61. package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
  62. package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
  63. package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
  64. package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
  65. package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
  66. package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
  67. package/rules/text/policy/package_json/package_json.rego +14 -52
  68. package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
  69. package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  70. package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
  71. package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
  72. package/rules/text/policy/vscode_settings/vscode_settings.rego +20 -42
  73. package/skills/adr-normalize/SKILL.md +2 -2
  74. package/rules/security/fix/gitleaks/check.mjs +0 -25
  75. package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +0 -12
  76. package/rules/security/policy/gitleaks/gitleaks.rego +0 -17
  77. package/rules/security/policy/gitleaks/target.json +0 -8
@@ -1,71 +1,48 @@
1
- # Порт перевірки `package.json` з `npm/scripts/check-image-compress.mjs`
2
- # (image-compress.mdc).
1
+ # Перевірка `package.json` (image-compress.mdc).
3
2
  #
4
- # Запуск (локально):
5
- # conftest test package.json -p npm/policy/image_compress \
6
- # --namespace image_compress.package_json
3
+ # Канон надходить через --data: { "template": { "contains": ..., "deny": ... } }
4
+ # Структура --data сформована з template/package.json.{contains,deny}.json.
7
5
  #
8
- # Перевіряє: скрипт `lint-image` викликає `npx @nitra/minify-image` з `--src=.`
9
- # і `--write`, без `--avif` (AVIF окреме правило); агрегатор `lint` (якщо є)
10
- # містить `bun run lint-image`; `@nitra/minify-image` НЕ в dependencies / devDependencies.
11
- #
12
- # FS-перевірки (`.minify-image-cache.tsv` legacy-файл, `.gitignore` правил для
13
- # `.n-minify-image.tsv`) — у JS.
14
- #
15
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
6
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
7
+ # - `--avif` ЗАБОРОНЕНИЙ підрядок у `lint-image` (anti-contains);
8
+ # - агрегатор `lint` (якщо `lint-image` присутній) має містити `bun run lint-image`.
18
9
  package image_compress.package_json
19
10
 
20
11
  import rego.v1
21
12
 
22
- minify_pkg := "@nitra/minify-image"
23
-
24
- dep_template := concat(" ", [
25
- "package.json: %q не повинен бути в %s —",
26
- "використовуй npx (image-compress.mdc)",
27
- ])
28
-
29
- avif_in_lint_image_template := concat(" ", [
30
- "package.json: lint-image не має містити `--avif` —",
31
- "AVIF-генерацію виконує check image-avif (image-compress.mdc)",
32
- ])
33
-
34
- # ── deny: lint-image ──────────────────────────────────────────────────────
13
+ # ── deny: scripts.<name> має містити кожен substring з template.contains ─
35
14
 
36
15
  deny contains msg if {
37
- scripts := object.get(input, "scripts", {})
38
- not "lint-image" in object.keys(scripts)
39
- msg := "package.json: відсутній scripts.lint-image (image-compress.mdc)"
16
+ some script_name, needles in data.template.contains.scripts
17
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
18
+ some needle in needles
19
+ not contains(actual, needle)
20
+ msg := sprintf("package.json: scripts.%s має містити %q (image-compress.mdc)", [script_name, needle])
40
21
  }
41
22
 
42
- deny contains msg if {
43
- lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
44
- lint_image != ""
45
- not contains(lint_image, sprintf("npx %s", [minify_pkg]))
46
- msg := sprintf("package.json: lint-image має викликати `npx %s` (image-compress.mdc)", [minify_pkg])
47
- }
23
+ # ── deny: top-level deps/devDeps з template.deny ─────────────────────────
48
24
 
49
25
  deny contains msg if {
50
- lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
51
- contains(lint_image, sprintf("npx %s", [minify_pkg]))
52
- not has_src_flag(lint_image)
53
- msg := "package.json: lint-image має містити `--src=.` (image-compress.mdc)"
26
+ some pkg, reason in data.template.deny.dependencies
27
+ pkg in object.keys(object.get(input, "dependencies", {}))
28
+ msg := sprintf("package.json: dependencies.%s — %s", [pkg, reason])
54
29
  }
55
30
 
56
31
  deny contains msg if {
57
- lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
58
- contains(lint_image, sprintf("npx %s", [minify_pkg]))
59
- not contains(lint_image, "--write")
60
- msg := "package.json: lint-image має містити `--write` (image-compress.mdc)"
32
+ some pkg, reason in data.template.deny.devDependencies
33
+ pkg in object.keys(object.get(input, "devDependencies", {}))
34
+ msg := sprintf("package.json: devDependencies.%s — %s", [pkg, reason])
61
35
  }
62
36
 
63
- deny contains avif_in_lint_image_template if {
37
+ # ── deny: `--avif` заборонений у `lint-image` (anti-contains, у rego) ────
38
+
39
+ deny contains msg if {
64
40
  lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
65
41
  contains(lint_image, "--avif")
42
+ msg := "package.json: lint-image не має містити `--avif` — AVIF-генерацію виконує check image-avif (image-compress.mdc)"
66
43
  }
67
44
 
68
- # ── deny: агрегований `lint` має кликати `bun run lint-image` ─────────────
45
+ # ── deny: агрегатор `lint` (якщо `lint-image` є) ─────────────────────────
69
46
 
70
47
  deny contains msg if {
71
48
  "lint-image" in object.keys(object.get(input, "scripts", {}))
@@ -74,21 +51,3 @@ deny contains msg if {
74
51
  not contains(lint, "bun run lint-image")
75
52
  msg := "package.json: агрегований `lint` має містити `bun run lint-image` (image-compress.mdc)"
76
53
  }
77
-
78
- # ── deny: `@nitra/minify-image` НЕ в dependencies/devDependencies ────────
79
-
80
- deny contains msg if {
81
- minify_pkg in object.keys(object.get(input, "dependencies", {}))
82
- msg := sprintf(dep_template, [minify_pkg, "dependencies"])
83
- }
84
-
85
- deny contains msg if {
86
- minify_pkg in object.keys(object.get(input, "devDependencies", {}))
87
- msg := sprintf(dep_template, [minify_pkg, "devDependencies"])
88
- }
89
-
90
- # ── helpers ────────────────────────────────────────────────────────────────
91
-
92
- has_src_flag(s) if contains(s, "--src=.")
93
-
94
- has_src_flag(s) if contains(s, "--src .")
@@ -0,0 +1,5 @@
1
+ {
2
+ "scripts": {
3
+ "lint-image": ["npx @nitra/minify-image", "--src=.", "--write"]
4
+ }
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "dependencies": {
3
+ "@nitra/minify-image": "не повинен бути в dependencies — використовуй npx (image-compress.mdc)"
4
+ },
5
+ "devDependencies": {
6
+ "@nitra/minify-image": "не повинен бути в devDependencies — використовуй npx (image-compress.mdc)"
7
+ }
8
+ }
@@ -1,28 +1,15 @@
1
- # Порт перевірки залежностей `package.json` з `npm/scripts/check-js-bun-db.mjs`
2
- # (js-bun-db.mdc).
1
+ # Перевірка `dependencies` (js-bun-db.mdc).
3
2
  #
4
- # Запуск (локально, для будь-якого `package.json` у дереві):
5
- # conftest test path/to/package.json -p npm/policy/js_bun_db \
6
- # --namespace js_bun_db.package_json
7
- #
8
- # Перевіряє: у `dependencies` не повинно бути `pg`, `pg-format`, `mysql2` —
9
- # заміна на Bun native SQL (https://bun.com/docs/runtime/sql).
10
- #
11
- # AST-скан коду (`new SQL(...)` всередині функцій, `unsafe()` без маркера
12
- # `// allow-unsafe`, pg-leftover виклики, динамічні `IN (…)` через `.join(',')`)
13
- # лишається у JS (потребує парсингу `.js` / `.ts` через oxc-parser).
14
- #
15
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "deny": ... } }
4
+ # Структура --data сформована з template/package.json.deny.json.
5
+ # AST-скан коду (`new SQL(...)` у функціях, `unsafe()` без маркера, pg-leftover,
6
+ # динамічні `IN (…)` через `.join(',')`) лишається у JS.
18
7
  package js_bun_db.package_json
19
8
 
20
9
  import rego.v1
21
10
 
22
- forbidden_dependencies := {"pg", "pg-format", "mysql2"}
23
-
24
11
  deny contains msg if {
25
- some pkg_name in forbidden_dependencies
26
- pkg_name in object.keys(object.get(input, "dependencies", {}))
27
- msg := sprintf("dependencies містить заборонений %q — заміни на Bun native SQL (js-bun-db.mdc)", [pkg_name])
12
+ some pkg, reason in data.template.deny.dependencies
13
+ pkg in object.keys(object.get(input, "dependencies", {}))
14
+ msg := sprintf("dependencies.%s %s", [pkg, reason])
28
15
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "dependencies": {
3
+ "pg": "заміни на Bun native SQL (js-bun-db.mdc)",
4
+ "pg-format": "заміни на Bun native SQL — без ручного форматування (js-bun-db.mdc)",
5
+ "mysql2": "заміни на Bun native SQL (js-bun-db.mdc)"
6
+ }
7
+ }
@@ -1,37 +1,15 @@
1
- # Перевірка `dependencies` для правила `js-bun-redis.mdc` — паралель до
2
- # `npm/policy/js_bun_db/package_json/package_json.rego`.
1
+ # Перевірка `dependencies` (js-bun-redis.mdc).
3
2
  #
4
- # Запуск (локально, для будь-якого `package.json` у дереві):
5
- # conftest test path/to/package.json -p npm/policy/js_bun_redis \
6
- # --namespace js_bun_redis.package_json
7
- #
8
- # Перевіряє: у `dependencies` не повинно бути `ioredis`, `node-redis`,
9
- # `redis` або жодного з підпакетів `@redis/*` — заміна на Bun native Redis
10
- # (https://bun.com/docs/runtime/redis).
11
- #
12
- # AST-скан коду (`import` / `require` / dynamic `import()` тих самих пакетів)
13
- # лишається у `npm/scripts/check-js-bun-redis.mjs` (потребує `oxc-parser`).
14
- #
15
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "deny": ... } }
4
+ # Структура --data сформована з template/package.json.deny.json.
5
+ # AST-скан коду (`import`/`require`/dynamic `import()` тих самих пакетів)
6
+ # лишається у `js-bun-redis/fix/imports/check.mjs`.
18
7
  package js_bun_redis.package_json
19
8
 
20
9
  import rego.v1
21
10
 
22
- forbidden_dependencies := {
23
- "ioredis",
24
- "node-redis",
25
- "redis",
26
- "@redis/client",
27
- "@redis/json",
28
- "@redis/search",
29
- "@redis/time-series",
30
- "@redis/bloom",
31
- }
32
-
33
11
  deny contains msg if {
34
- some pkg_name in forbidden_dependencies
35
- pkg_name in object.keys(object.get(input, "dependencies", {}))
36
- msg := sprintf("dependencies містить заборонений %q — заміни на Bun native Redis (js-bun-redis.mdc)", [pkg_name])
12
+ some pkg, reason in data.template.deny.dependencies
13
+ pkg in object.keys(object.get(input, "dependencies", {}))
14
+ msg := sprintf("dependencies.%s %s", [pkg, reason])
37
15
  }
@@ -0,0 +1,12 @@
1
+ {
2
+ "dependencies": {
3
+ "ioredis": "заміни на Bun native Redis (js-bun-redis.mdc)",
4
+ "node-redis": "заміни на Bun native Redis (js-bun-redis.mdc)",
5
+ "redis": "заміни на Bun native Redis (js-bun-redis.mdc)",
6
+ "@redis/client": "заміни на Bun native Redis (js-bun-redis.mdc)",
7
+ "@redis/json": "заміни на Bun native Redis (js-bun-redis.mdc)",
8
+ "@redis/search": "заміни на Bun native Redis (js-bun-redis.mdc)",
9
+ "@redis/time-series": "заміни на Bun native Redis (js-bun-redis.mdc)",
10
+ "@redis/bloom": "заміни на Bun native Redis (js-bun-redis.mdc)"
11
+ }
12
+ }
@@ -1,38 +1,44 @@
1
- # Перевірка `.jscpd.json` для js-lint (js-lint.mdc).
1
+ # Перевірка `.jscpd.json` (js-lint.mdc).
2
2
  #
3
- # JS-частина лишається для FS/cross-file: наявність workflow, flat ESLint config,
4
- # `.oxlintrc.json` проти embedded canonical snapshot, `knip.json` autofill.
5
- # Цей пакет покриває лише структуру одного JSON-документа.
6
- #
7
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
8
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
9
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/.jscpd.json.snippet.json.
5
+ # Особливості:
6
+ # - `reporters` — subset-of (масив string)
7
+ # - `minLines` >= expected (semantic: дозволяється більше, не менше)
8
+ # - `gitignore`/`exitCode` exact match
10
9
  package js_lint.jscpd
11
10
 
12
11
  import rego.v1
13
12
 
13
+ # Top-level scalar leafs (exact match) для gitignore + exitCode.
14
14
  deny contains msg if {
15
- input.gitignore != true
16
- msg := ".jscpd.json має містити \"gitignore\": true (js-lint.mdc)"
15
+ some key, expected_value in data.template.snippet
16
+ key != "minLines"
17
+ not is_array(expected_value)
18
+ actual := object.get(input, key, null)
19
+ actual != expected_value
20
+ msg := sprintf(".jscpd.json має містити \"%s\": %v (js-lint.mdc)", [key, expected_value])
17
21
  }
18
22
 
23
+ # Array subset-of для reporters.
19
24
  deny contains msg if {
20
- input.exitCode != 1
21
- msg := ".jscpd.json має містити \"exitCode\": 1 (інакше CI не впаде на клонах) (js-lint.mdc)"
25
+ some field, expected_values in data.template.snippet
26
+ is_array(expected_values)
27
+ actual_set := {v | some v in object.get(input, field, [])}
28
+ some required in expected_values
29
+ not required in actual_set
30
+ msg := sprintf(".jscpd.json має містити \"%s\": [\"%s\"] (js-lint.mdc)", [field, required])
22
31
  }
23
32
 
33
+ # minLines: must be number and >= expected.
24
34
  deny contains msg if {
25
- not "console" in {r | some r in object.get(input, "reporters", [])}
26
- msg := ".jscpd.json має містити \"reporters\": [\"console\"] або масив із \"console\" (js-lint.mdc)"
35
+ expected := data.template.snippet.minLines
36
+ actual := object.get(input, "minLines", null)
37
+ not is_valid_min_lines(actual, expected)
38
+ msg := sprintf(".jscpd.json має містити \"minLines\" як число >= %d (js-lint.mdc)", [expected])
27
39
  }
28
40
 
29
- deny contains msg if {
30
- not is_number(object.get(input, "minLines", null))
31
- msg := ".jscpd.json має містити \"minLines\" як число >= 25 (js-lint.mdc)"
32
- }
33
-
34
- deny contains msg if {
35
- is_number(input.minLines)
36
- input.minLines < 25
37
- msg := ".jscpd.json має містити \"minLines\" як число >= 25 (js-lint.mdc)"
41
+ is_valid_min_lines(actual, expected) if {
42
+ is_number(actual)
43
+ actual >= expected
38
44
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "gitignore": true,
3
+ "exitCode": 1,
4
+ "reporters": ["console"],
5
+ "minLines": 25
6
+ }
@@ -1,27 +1,35 @@
1
- # Порт перевірки `.github/workflows/lint-js.yml` з `npm/scripts/check-js-lint.mjs`
2
- # (js-lint.mdc) — структурні очікування `verifyLintJsWorkflowStructure`.
1
+ # Перевірка `.github/workflows/lint-js.yml` (js-lint.mdc).
3
2
  #
4
- # Запуск (локально):
5
- # conftest test .github/workflows/lint-js.yml -p npm/policy/js_lint \
6
- # --namespace js_lint.lint_js_yml
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/lint-js.yml.snippet.yml.
5
+ # Required uses + run substrings зчитуються з template's eslint-job steps.
6
+ # Універсальні workflow-перевірки — у `ga.workflow_common`.
7
7
  #
8
- # Перевіряє: є крок `actions/checkout@v6` з `with.persist-credentials: false`,
9
- # є крок `./.github/actions/setup-bun-deps`, у `run` є `bunx oxlint`, `bunx eslint .`,
10
- # `bunx jscpd .`, у `run` НЕМАЄ `oxlint --fix` чи `eslint --fix` (CI не повинен
11
- # редагувати код).
12
- #
13
- # Універсальні workflow-перевірки (concurrency, заборонені setup-bun/cache, shell
14
- # line-continuation) — у `ga.workflow_common`. Дубль JS-перевірок у `lint.yml` —
15
- # у JS-частині `check-js-lint.mjs` (потребує другого workflow-файлу).
16
- #
17
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
18
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
19
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
8
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse anti-patterns):
9
+ # - `oxlint --fix` / `eslint --fix` у CI заборонено;
10
+ # - actions/checkout@v6 має мати `with.persist-credentials: false`.
20
11
  package js_lint.lint_js_yml
21
12
 
22
13
  import rego.v1
23
14
 
24
- # Усі кроки з усіх jobs для substring-перевірок.
15
+ # Required `uses:` зі templateфільтруємо тільки кроки з uses.
16
+ expected_uses_set contains u if {
17
+ some step in data.template.snippet.jobs.eslint.steps
18
+ u := object.get(step, "uses", "")
19
+ u != ""
20
+ }
21
+
22
+ # Required `run:` substrings (per-line з template).
23
+ expected_run_substrings contains line if {
24
+ some step in data.template.snippet.jobs.eslint.steps
25
+ r := object.get(step, "run", "")
26
+ r != ""
27
+ some raw in split(r, "\n")
28
+ line := trim_space(raw)
29
+ line != ""
30
+ }
31
+
32
+ # Аліаси на input.
25
33
  all_steps contains step if {
26
34
  some job in object.get(input, "jobs", {})
27
35
  some step in object.get(job, "steps", [])
@@ -37,46 +45,30 @@ all_run_blob := concat("\n", [r |
37
45
  r := step_run_text(step)
38
46
  ])
39
47
 
40
- # ── deny: required uses ────────────────────────────────────────────────────
41
-
42
- deny contains msg if {
43
- not contains(all_uses_blob, "actions/checkout@v6")
44
- msg := "lint-js.yml: відсутній крок uses: actions/checkout@v6 (js-lint.mdc)"
45
- }
46
-
47
- deny contains msg if {
48
- not contains(all_uses_blob, "./.github/actions/setup-bun-deps")
49
- msg := "lint-js.yml: відсутній крок uses: ./.github/actions/setup-bun-deps (js-lint.mdc)"
50
- }
48
+ # ── deny: required uses ─────────────────────────────────────────────────
51
49
 
52
50
  deny contains msg if {
53
- not has_checkout_persist_credentials_false
54
- msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
51
+ some required_use in expected_uses_set
52
+ not contains(all_uses_blob, required_use)
53
+ msg := sprintf("lint-js.yml: відсутній крок uses: %s (js-lint.mdc)", [required_use])
55
54
  }
56
55
 
57
- # ── deny: required run substrings ─────────────────────────────────────────
56
+ # ── deny: required run substrings ───────────────────────────────────────
58
57
 
59
58
  deny contains msg if {
60
- not contains(all_run_blob, "bunx oxlint")
61
- msg := "lint-js.yml: у run немає `bunx oxlint` (js-lint.mdc)"
59
+ some required_run in expected_run_substrings
60
+ not contains(all_run_blob, required_run)
61
+ msg := sprintf("lint-js.yml: у run немає %q (js-lint.mdc)", [required_run])
62
62
  }
63
63
 
64
- deny contains msg if {
65
- not contains(all_run_blob, "bunx eslint .")
66
- msg := "lint-js.yml: у run немає `bunx eslint .` (js-lint.mdc)"
67
- }
64
+ # ── deny: actions/checkout@v6 has persist-credentials: false (inverse) ──
68
65
 
69
66
  deny contains msg if {
70
- not contains(all_run_blob, "bunx jscpd .")
71
- msg := "lint-js.yml: у run немає `bunx jscpd .` (js-lint.mdc)"
72
- }
73
-
74
- deny contains msg if {
75
- not contains(all_run_blob, "bunx knip")
76
- msg := "lint-js.yml: у run немає `bunx knip` (js-lint.mdc)"
67
+ not has_checkout_persist_credentials_false
68
+ msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
77
69
  }
78
70
 
79
- # ── deny: --fix у CI заборонено ───────────────────────────────────────────
71
+ # ── deny: --fix у CI заборонено (inverse) ───────────────────────────────
80
72
 
81
73
  deny contains msg if {
82
74
  regex.match(`bunx\s+oxlint[^\n]*--fix`, all_run_blob)
@@ -88,7 +80,7 @@ deny contains msg if {
88
80
  msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js-lint.mdc)"
89
81
  }
90
82
 
91
- # ── helpers ────────────────────────────────────────────────────────────────
83
+ # ── helpers ─────────────────────────────────────────────────────────────
92
84
 
93
85
  has_checkout_persist_credentials_false if {
94
86
  some step in all_steps
@@ -0,0 +1,44 @@
1
+ name: Lint JS
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev
7
+ - main
8
+ paths:
9
+ - '**/*.js'
10
+ - '**/*.mjs'
11
+ - '**/*.cjs'
12
+ - '**/*.jsx'
13
+ - '**/*.ts'
14
+ - '**/*.tsx'
15
+ - '**/*.vue'
16
+ - '**/eslint.config.*'
17
+
18
+ pull_request:
19
+ branches:
20
+ - dev
21
+ - main
22
+
23
+ concurrency:
24
+ group: ${{ github.ref }}-${{ github.workflow }}
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ eslint:
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: read
32
+ steps:
33
+ - uses: actions/checkout@v6
34
+ with:
35
+ persist-credentials: false
36
+
37
+ - uses: ./.github/actions/setup-bun-deps
38
+
39
+ - name: Eslint
40
+ run: |
41
+ bunx oxlint
42
+ bunx eslint .
43
+ bunx jscpd .
44
+ bunx knip --no-config-hints
@@ -1,47 +1,36 @@
1
- # Порт перевірок `package.json` з `npm/scripts/check-js-lint.mjs` (js-lint.mdc).
1
+ # Перевірка `package.json` (js-lint.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test package.json -p npm/policy/js_lint --namespace js_lint.package_json
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/package.json.snippet.json
5
+ # (canonical `type` + `scripts.lint-js`).
5
6
  #
6
- # Перевіряє: канонічний `lint-js` скрипт, `@nitra/eslint-config` 3.9.2 у
7
- # `devDependencies`, `engines.node >= 24`, `engines.bun >= 1.3`, `type: "module"`.
8
- #
9
- # Перевірка `.oxlintrc.json` проти канонічного JSON (`utils/oxlint-canonical.json`)
10
- # і дубля JS-перевірок у `lint.yml` — у JS (потребує читання другого файлу
11
- # і порівняння глибокої структури проти embedded snapshot).
12
- #
13
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
14
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
15
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
7
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse, не виноситься у template):
8
+ # - `engines.node` >= 24, `engines.bun` >= 1.3 (semver range parsing);
9
+ # - `@nitra/eslint-config` >= 3.9.2 (semver range parsing).
16
10
  package js_lint.package_json
17
11
 
18
12
  import rego.v1
19
13
 
20
- canonical_lint_js := "bunx oxlint --fix && bunx eslint --fix . && bunx jscpd . && bunx knip --no-config-hints"
21
-
22
- # ── deny: `lint-js` скрипт ─────────────────────────────────────────────────
23
-
24
- deny contains msg if {
25
- scripts := object.get(input, "scripts", {})
26
- not "lint-js" in object.keys(scripts)
27
- msg := "package.json: відсутній скрипт `lint-js` (js-lint.mdc)"
28
- }
14
+ # ── deny: top-level scalar leafs (type) ─────────────────────────────────
29
15
 
30
16
  deny contains msg if {
31
- lint_js := object.get(object.get(input, "scripts", {}), "lint-js", "")
32
- lint_js != ""
33
- normalize_lint_js(lint_js) != canonical_lint_js
34
- msg := sprintf("package.json: lint-js має бути %q (js-lint.mdc)", [canonical_lint_js])
17
+ some key, expected_value in data.template.snippet
18
+ not is_object(expected_value)
19
+ actual := object.get(input, key, null)
20
+ actual != expected_value
21
+ msg := sprintf("package.json: \"%s\" має бути %q (js-lint.mdc)", [key, expected_value])
35
22
  }
36
23
 
37
- # ── deny: type: "module" ──────────────────────────────────────────────────
24
+ # ── deny: scripts (nested) — exact match із normalize ──────────────────
38
25
 
39
26
  deny contains msg if {
40
- object.get(input, "type", null) != "module"
41
- msg := "package.json: \"type\" має бути \"module\" (js-lint.mdc)"
27
+ some script_name, expected in data.template.snippet.scripts
28
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
29
+ normalize_script(actual) != expected
30
+ msg := sprintf("package.json: scripts.%s має бути %q (js-lint.mdc)", [script_name, expected])
42
31
  }
43
32
 
44
- # ── deny: engines ──────────────────────────────────────────────────────────
33
+ # ── deny: engines.node >= 24 (inverse, у rego) ──────────────────────────
45
34
 
46
35
  deny contains msg if {
47
36
  engines := object.get(input, "engines", {})
@@ -55,7 +44,7 @@ deny contains msg if {
55
44
  msg := "package.json: engines.bun має бути >= 1.3 (js-lint.mdc)"
56
45
  }
57
46
 
58
- # ── deny: @nitra/eslint-config 3.9.2 ────────────────────────────────────
47
+ # ── deny: @nitra/eslint-config >= 3.9.2 (inverse) ───────────────────────
59
48
 
60
49
  deny contains msg if {
61
50
  dev := object.get(input, "devDependencies", {})
@@ -70,20 +59,15 @@ deny contains msg if {
70
59
  msg := sprintf("package.json: @nitra/eslint-config має бути >= 3.9.2 (зараз %q) (js-lint.mdc)", [range])
71
60
  }
72
61
 
73
- # ── helpers ────────────────────────────────────────────────────────────────
62
+ # ── helpers ──────────────────────────────────────────────────────────────
74
63
 
75
- # Нормалізація `lint-js`: trim + одиничні пробіли (як у JS).
76
- normalize_lint_js(s) := regex.replace(trim_space(s), `\s+`, " ")
64
+ normalize_script(s) := regex.replace(trim_space(s), `\s+`, " ")
77
65
 
78
- # `engines.node`: дозволяється `>=24`, `^24`, `24.x`, `24.0.0` тощо. Дістаємо
79
- # першу мажорну цифру; вона має бути ≥ 24.
80
66
  engines_node_meets(spec) if {
81
67
  major := first_major(spec)
82
68
  major >= 24
83
69
  }
84
70
 
85
- # `engines.bun`: дозволяється `>=1.3`, `^1.3.0`, `1.3.x` тощо. Перша мажор-мінор
86
- # пара має бути ≥ 1.3.
87
71
  engines_bun_meets(spec) if {
88
72
  parts := split_to_numbers(spec)
89
73
  count(parts) >= 2
@@ -97,7 +81,6 @@ engines_bun_meets(spec) if {
97
81
  parts[1] >= 3
98
82
  }
99
83
 
100
- # `@nitra/eslint-config`: ≥ 3.9.2; `workspace:*` теж OK.
101
84
  eslint_config_meets_min(range) if startswith(trim_space(range), "workspace:")
102
85
 
103
86
  eslint_config_meets_min(range) if {
@@ -121,15 +104,12 @@ eslint_config_meets_min(range) if {
121
104
  parts[2] >= 2
122
105
  }
123
106
 
124
- # Перша мажорна цифра з рядка-діапазону (наприклад `^24.1.0` → 24).
125
107
  first_major(spec) := major if {
126
108
  parts := split_to_numbers(spec)
127
109
  count(parts) >= 1
128
110
  major := parts[0]
129
111
  }
130
112
 
131
- # Розкидати рядок версії на список чисел (відкидаючи range-оператори і нечислові
132
- # фрагменти). `^24.1.0` → [24, 1, 0]; `>=1.3` → [1, 3]; `workspace:*` → [].
133
113
  split_to_numbers(spec) := nums if {
134
114
  tokens := regex.split(`\D+`, spec)
135
115
  non_empty := [t | some t in tokens; t != ""]
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "scripts": {
4
+ "lint-js": "bunx oxlint --fix && bunx eslint --fix . && bunx jscpd . && bunx knip --no-config-hints"
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "dbaeumer.vscode-eslint",
4
+ "github.vscode-github-actions",
5
+ "oxc.oxc-vscode"
6
+ ]
7
+ }