@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
@@ -78,7 +78,7 @@ if [ -f "$STATE_FILE" ]; then
78
78
  fi
79
79
 
80
80
  THRESHOLD="${ADR_NORMALIZE_THRESHOLD:-30}"
81
- BATCH_SIZE="${ADR_NORMALIZE_BATCH:-30}"
81
+ BATCH_SIZE="${ADR_NORMALIZE_BATCH:-10}"
82
82
  DRY_RUN="${ADR_NORMALIZE_DRY:-0}"
83
83
 
84
84
  # Detects whether a markdown file is a draft: has YAML frontmatter with `session:` field.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,91 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.26] - 2026-05-17
8
+
9
+ ### Changed
10
+
11
+ - `security` rule: міграція з gitleaks на TruffleHog. Канонічний `lint-security` тепер `trufflehog filesystem . --no-update --exclude-paths .trufflehog-exclude --results=verified,unknown --fail`; allowlist переїхав із TOML-файлу `.gitleaks.toml` (`[extend].useDefault=true` + `[allowlist].paths`) у plain-text `.trufflehog-exclude` (regex-pattern на рядок). Bump `security.mdc` version `1.1` → `2.0`.
12
+
13
+ ### Added
14
+
15
+ - `npm/rules/security/fix/trufflehog/check.mjs` + `template/.trufflehog-exclude.snippet.txt` — JS-частина правила перевіряє існування `.trufflehog-exclude` та subset канонічних patterns через `checkTextSubset` (Rego не пасує plain-text-формату).
16
+ - `npm/rules/security/policy/lint_security_yml/` — Rego policy `security.lint_security_yml` із template `lint-security.yml.snippet.yml`; перевіряє, що `.github/workflows/lint-security.yml` містить крок з `uses: trufflesecurity/trufflehog@main`. У `security.mdc` inline-YAML замінено на template-link для single-source-of-truth (патерн із `php`/`style-lint`/`js-lint`). Workflow обовʼязковий (`target.json: required=true` + `missingMessage`), у корені cursor створено `.github/workflows/lint-security.yml` за каноном.
17
+
18
+ ### Removed
19
+
20
+ - `npm/rules/security/fix/gitleaks/` + `npm/rules/security/policy/gitleaks/` (концерн gitleaks повністю видалено разом з Rego policy `security.gitleaks`).
21
+ - Кореневий `.gitleaks.toml` (замінено на `.trufflehog-exclude`).
22
+
23
+ ## [1.13.25] - 2026-05-17
24
+
25
+ ### Added
26
+
27
+ - `js-lint` rule template/ міграція (Phase 15, фінал): 4 концерни — `jscpd` (snippet з minLines >=N semantic), `vscode_extensions` (snippet-array), `package_json` (partial — `type`+`scripts.lint-js` у template; engines + eslint-config semver ranges у rego), `lint_js_yml` (full-canon з required uses + per-line run substrings з template's eslint-job steps; --fix anti-patterns + checkout persist-credentials у rego).
28
+
29
+ ### Closed
30
+
31
+ - Template/ migration scope (Phases 1-15): 19 з 39 template-eligible концернів мігровано у попередніх фазах + 17 нових у Phase 6-15 = усі 39 завершено (100%). Залишилися лише non-eligible (AST-walks, multi-kind YAML, cross-file gating) — за дизайном живуть у JS/rego inline.
32
+
33
+ ## [1.13.24] - 2026-05-17
34
+
35
+ ### Added
36
+
37
+ - `text` rule template/ міграція (Phase 14): 6 концернів — `cspell` (snippet + contains + deny з `@cspell/dict-` як forbidden substrings), `markdownlint` (3-level walker для `config.MD024.siblings_only`), `oxfmtrc` (scalar leafs + array subset-of; required_keys presence лишається в rego), `package_json` (top-level deny + deps/devDeps deny; `@nitra/cspell-dict` semver range у rego), `vscode_extensions` (snippet-array), `vscode_settings` (top-level leafs + per-language blocks).
38
+
39
+ ## [1.13.23] - 2026-05-17
40
+
41
+ ### Added
42
+
43
+ - `style-lint` rule template/ міграція (Phase 13): 4 policy концерни — `package_json` (contains + 2-level snippet для `stylelint.extends`; `@nitra/stylelint-config` presence лишається в rego), `vscode_extensions` (snippet-array), `vscode_settings` (top-level leaf walker для `css/less/scss.validate`), `lint_style_yml` (full-canon з substring-маркером з template's stylelint-job steps). `fix/tooling` (`.stylelintignore` partial) лишається JS-managed.
44
+
45
+ ## [1.13.22] - 2026-05-17
46
+
47
+ ### Added
48
+
49
+ - `image-avif.package_json` template/ міграція (Phase 12): typo-keys у `template/package.json.deny.json` (`@nitra/minify-image.disabled-avif` як приклад typo до `disable-avif`). Type-перевірки (inverse-patterns) лишилися в rego.
50
+
51
+ ## [1.13.21] - 2026-05-17
52
+
53
+ ### Added
54
+
55
+ - `js-run` rule template/ міграція (Phase 11): 3 концерни (інвентар недорахував `jsconfig`) — `configmap` (contains, OTEL_RESOURCE_ATTRIBUTES substrings), `package_json` (deny на bunyan/@nitra/bunyan у deps/devDeps), `jsconfig` (snippet з generic 2-level walker + top-level array як множина для `include`).
56
+ - Drift-тести у кожному `*_test.rego`.
57
+
58
+ ## [1.13.20] - 2026-05-17
59
+
60
+ ### Added
61
+
62
+ - `abie.clean_merged_ignore_branches` template/ міграція (Phase 10): action marker (`uses:` substring) + required `ignore_branches` tokens (`dev,ua`) тепер у `template/clean-merged-branch.yml.snippet.yml`. Rego читає expected step із template's `jobs.cleanup_old_branches.steps[0]`. Drift test покриває зміну required-branches.
63
+ - `abie.mdc` — inline `ignore_branches` фрагмент замінено на template-link.
64
+
65
+ ## [1.13.19] - 2026-05-17
66
+
67
+ ### Added
68
+
69
+ - `docker` rule template/ міграція (Phase 9): `docker.package_json` (snippet, scripts.lint-docker з trim_space, conditional на наявність) + `docker.lint_docker_yml` (full-canon — paths, required uses, run substrings з template's steps).
70
+ - `docker.mdc` — 2 inline-блоки замінено на template-links.
71
+
72
+ ## [1.13.18] - 2026-05-17
73
+
74
+ ### Added
75
+
76
+ - `js-bun-db.package_json` + `js-bun-redis.package_json` template/ міграція (Phase 8): `template/package.json.deny.json` (forbidden deps з причинами). Rego — простий deny-walker. 2 нових `*_test.rego` (8 тестів).
77
+
78
+ ## [1.13.17] - 2026-05-17
79
+
80
+ ### Added
81
+
82
+ - `php` rule template/ міграція (Phase 7): `php.package_json` (fragment, contains-walker для `lint-php`) + `php.lint_php_yml` (full-canon, substring-маркер `bun run lint-php` з template's php-job steps). 2 нових `*_test.rego` (8 тестів).
83
+ - `php.mdc` — 2 inline-блоки замінено на template-links.
84
+
85
+ ## [1.13.16] - 2026-05-17
86
+
87
+ ### Added
88
+
89
+ - `image-compress.package_json` template/ міграція (Phase 6): `template/package.json.{contains,deny}.json` + новий `package_json_test.rego` (10 тестів). Generic contains-walker для substring-перевірок lint-image + generic deny-walker для заборонених deps; інверс-патерни (`--avif` заборонений підрядок + аґреґатор `lint` має містити `bun run lint-image`) лишилися в rego.
90
+ - `image-compress.mdc` — inline `package.json` snippet замінено на template-links.
91
+
7
92
  ## [1.13.15] - 2026-05-17
8
93
 
9
94
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.15",
3
+ "version": "1.13.26",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -148,10 +148,7 @@ KVCMS_URL=http://kvcms-hl.ua-apruv.svc.abie-ua.internal:8080
148
148
 
149
149
  У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev** та **ua** (разом з іншими гілками, якщо потрібно):
150
150
 
151
- ```yaml title=".github/workflows/clean-merged-branch.yml (фрагмент)"
152
- with:
153
- ignore_branches: main,dev,ua
154
- ```
151
+ - abie-specific канон (action marker + required ignore_branches tokens): [clean-merged-branch.yml.snippet.yml](./policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml)
155
152
 
156
153
  ## Швидкий gate через conftest (Rego)
157
154
 
@@ -1,58 +1,42 @@
1
- # Порт перевірки `parseCleanMergedIgnoreBranches` + `ignoreBranchesIncludesRequired`
2
- # з `npm/scripts/check-abie.mjs` (abie.mdc): у workflow
3
- # `.github/workflows/clean-merged-branch.yml` крок з
4
- # `uses: phpdocker-io/github-actions-delete-abandoned-branches` має у
5
- # `with.ignore_branches` містити усі обовʼязкові токени `dev,ua`
6
- # (case-insensitive, кома-розділені).
1
+ # Перевірка `clean-merged-branch.yml` для abie-проєктів (abie.mdc).
7
2
  #
8
- # Запуск (локально):
9
- # conftest test .github/workflows/clean-merged-branch.yml \
10
- # -p npm/policy/abie/clean_merged_ignore_branches \
11
- # --namespace abie.clean_merged_ignore_branches
12
- #
13
- # JS authoritative (`check-abie.mjs`: `checkCleanMergedBranch`,
14
- # `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`); ця Rego —
15
- # швидкий gate для одиничного workflow YAML. Cross-file гейтинг (правило
16
- # `abie` у `.n-cursor.json`) — у JS.
17
- #
18
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
19
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
20
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/clean-merged-branch.yml.snippet.yml.
5
+ # Action-маркер (`uses:` substring) і required branches (parsed з template's
6
+ # `ignore_branches`) читаються з template. ga.clean_merged_branch перевіряє
7
+ # повний канон workflow окремо; цей пакет — лише abie-specific шар.
21
8
  package abie.clean_merged_ignore_branches
22
9
 
23
10
  import rego.v1
24
11
 
25
- # Обовʼязкові гілки в `ignore_branches` (узгоджено з `ABIE_REQUIRED_IGNORE_BRANCHES`).
26
- required_branches := {"dev", "ua"}
12
+ # Експектації з template's step (першого з steps).
13
+ expected_step := step if some step in data.template.snippet.jobs.cleanup_old_branches.steps
14
+
15
+ target_action_marker := expected_step.uses
27
16
 
28
- # Префікс `uses:` для GitHub Action, у якого читаємо `with.ignore_branches`.
29
- target_action_marker := "phpdocker-io/github-actions-delete-abandoned-branches"
17
+ required_branches := parsed_ignore_tokens(expected_step.with.ignore_branches)
30
18
 
31
- step_missing_msg := concat(" ", [
32
- "clean-merged-branch.yml: не знайдено крок з uses: phpdocker-io/github-actions-delete-abandoned-branches",
33
- "(abie.mdc)",
34
- ])
19
+ step_missing_msg := sprintf(
20
+ "clean-merged-branch.yml: не знайдено крок з uses: %s (abie.mdc)",
21
+ [target_action_marker],
22
+ )
35
23
 
36
- ignore_branches_missing_msg := concat(" ", [
37
- "clean-merged-branch.yml: не знайдено with.ignore_branches у кроці",
38
- "phpdocker-io/github-actions-delete-abandoned-branches (abie.mdc)",
39
- ])
24
+ ignore_branches_missing_msg := sprintf(
25
+ "clean-merged-branch.yml: не знайдено with.ignore_branches у кроці %s (abie.mdc)",
26
+ [target_action_marker],
27
+ )
40
28
 
41
- # ── deny: крок не знайдено ────────────────────────────────────────────────
29
+ # ── deny ─────────────────────────────────────────────────────────────────
42
30
 
43
31
  deny contains step_missing_msg if {
44
32
  count(target_steps) == 0
45
33
  }
46
34
 
47
- # ── deny: з step нема with.ignore_branches ────────────────────────────────
48
-
49
35
  deny contains ignore_branches_missing_msg if {
50
36
  count(target_steps) > 0
51
37
  not has_ignore_branches_value
52
38
  }
53
39
 
54
- # ── deny: ignore_branches не містить усіх обов'язкових токенів ────────────
55
-
56
40
  deny contains msg if {
57
41
  count(target_steps) > 0
58
42
  ignore_branches_value != ""
@@ -64,9 +48,8 @@ deny contains msg if {
64
48
  )
65
49
  }
66
50
 
67
- # ── helpers ───────────────────────────────────────────────────────────────
51
+ # ── helpers ──────────────────────────────────────────────────────────────
68
52
 
69
- # Усі steps з усіх jobs у workflow (підтримує jobs.<job>.steps[]).
70
53
  target_steps contains step if {
71
54
  some job in object.get(input, "jobs", {})
72
55
  some step in object.get(job, "steps", [])
@@ -75,7 +58,6 @@ target_steps contains step if {
75
58
  contains(uses, target_action_marker)
76
59
  }
77
60
 
78
- # Чи у знайдених steps хоча б у одного є with.ignore_branches непорожнім рядком.
79
61
  has_ignore_branches_value if {
80
62
  some step in target_steps
81
63
  v := object.get(object.get(step, "with", {}), "ignore_branches", null)
@@ -93,7 +75,6 @@ ignore_branches_value := values[0] if {
93
75
  count(values) > 0
94
76
  }
95
77
 
96
- # Розбирає `ignore_branches` як `,`-розділений список, нормалізує через trim+lower.
97
78
  parsed_ignore_tokens(value) := {lower(trim_space(part)) |
98
79
  some part in split(value, ",")
99
80
  trim_space(part) != ""
@@ -0,0 +1,9 @@
1
+ # abie-specific шар поверх ga.clean_merged_branch:
2
+ # у `clean-merged-branch.yml` крок phpdocker-io/github-actions-delete-abandoned-branches
3
+ # має містити `ignore_branches: dev,ua` (обовʼязкові гілки для abie-проєктів).
4
+ jobs:
5
+ cleanup_old_branches:
6
+ steps:
7
+ - uses: phpdocker-io/github-actions-delete-abandoned-branches
8
+ with:
9
+ ignore_branches: dev,ua
package/rules/adr/adr.mdc CHANGED
@@ -35,7 +35,7 @@ Stop-hook `normalize-decisions.sh` спрацьовує на тому самом
35
35
 
36
36
  - Виходить миттєво, якщо чернеток (`session:` у frontmatter) менше ніж **`ADR_NORMALIZE_THRESHOLD`** (default 30).
37
37
  - Виходить миттєво, якщо від попередньої спроби пройшло менше **`ADR_NORMALIZE_MIN_INTERVAL_HOURS`** годин (default 6) — щоб не крутитися щоразу, коли поріг постійний.
38
- - Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 30, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
38
+ - Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 10, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
39
39
  - Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
40
40
  - Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
41
41
 
@@ -60,7 +60,7 @@ LLM повертає масив операцій:
60
60
  | Змінна | Default | Призначення |
61
61
  | --- | --- | --- |
62
62
  | `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
63
- | `ADR_NORMALIZE_BATCH` | `30` | Максимум чернеток у одному виклику LLM. |
63
+ | `ADR_NORMALIZE_BATCH` | `10` | Максимум чернеток у одному виклику LLM. |
64
64
  | `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
65
65
  | `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
66
66
  | `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
@@ -101,61 +101,13 @@ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE
101
101
 
102
102
  Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`check-docker.mjs`**. Виклик **`hadolint`**: **`PATH`**, інакше **`docker run`** — спільна логіка **`npm/scripts/utils/docker-hadolint.mjs`**.
103
103
 
104
- ```json title="package.json"
105
- {
106
- "scripts": {
107
- "lint-docker": "n-cursor lint-docker"
108
- }
109
- }
110
- ```
104
+ - Канон `package.json#scripts.lint-docker`: [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
111
105
 
112
106
  Якщо правило **`docker`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **обов'язково** мають бути скрипт **`lint-docker`** і виклик **`bun run lint-docker`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor check bun`**.
113
107
 
114
108
  Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
115
109
 
116
- ```yaml title=".github/workflows/lint-docker.yml"
117
- name: Lint Docker
118
-
119
- on:
120
- push:
121
- branches:
122
- - dev
123
- - main
124
- paths:
125
- - '**/Dockerfile'
126
- - '**/*.Dockerfile'
127
- - '**/*.dockerfile'
128
-
129
- pull_request:
130
- branches:
131
- - dev
132
- - main
133
-
134
- concurrency:
135
- group: ${{ github.ref }}-${{ github.workflow }}
136
- cancel-in-progress: true
137
-
138
- jobs:
139
- lint-docker:
140
- runs-on: ubuntu-latest
141
- permissions:
142
- contents: read
143
- steps:
144
- - uses: actions/checkout@v6
145
- with:
146
- persist-credentials: false
147
-
148
- - name: Install hadolint
149
- run: |
150
- curl -sSL -o /tmp/hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64
151
- chmod +x /tmp/hadolint
152
- sudo mv /tmp/hadolint /usr/local/bin/hadolint
153
-
154
- - uses: ./.github/actions/setup-bun-deps
155
-
156
- - name: Lint Docker
157
- run: bun run lint-docker
158
- ```
110
+ - Канон: [lint-docker.yml.snippet.yml](./policy/lint_docker_yml/template/lint-docker.yml.snippet.yml)
159
111
 
160
112
  Узгоджуй версію hadolint **v2.12.0** з **`HADOLINT_IMAGE`** у **`npm/scripts/utils/docker-hadolint.mjs`**.
161
113
 
@@ -1,33 +1,32 @@
1
1
  # Перевірка `.github/workflows/lint-docker.yml` (docker.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .github/workflows/lint-docker.yml -p npm/policy/docker/lint_docker_yml \
5
- # --namespace docker.lint_docker_yml
6
- #
7
- # Canonical (docker.mdc):
8
- # - `on.push.paths` містить glob-и для Dockerfile (`**/Dockerfile`, `**/*.Dockerfile`,
9
- # `**/*.dockerfile`);
10
- # - крок `Install hadolint` з URL версії `v2.12.0` (узгоджено з `HADOLINT_IMAGE`
11
- # у `npm/scripts/utils/docker-hadolint.mjs`);
12
- # - крок `uses: ./.github/actions/setup-bun-deps` (canonical composite per ga.mdc;
13
- # прямі `oxen-sh/setup-bun`/`actions/cache`/`bun install` заборонено через
14
- # `ga.workflow_common`);
15
- # - крок `run: bun run lint-docker`.
16
- #
17
- # Універсальні workflow-перевірки (concurrency, заборонені setup-bun/cache/install,
18
- # shell line-continuation) — у `ga.workflow_common`.
19
- #
20
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/lint-docker.yml.snippet.yml.
5
+ # Path-маркери, hadolint версія (substring "v2.12.0" у run), setup-bun-deps
6
+ # composite, `bun run lint-docker` substring — все читається з template's snippet.
7
+ # Універсальні workflow-перевірки — у `ga.workflow_common`.
21
8
  package docker.lint_docker_yml
22
9
 
23
10
  import rego.v1
24
11
 
25
- required_push_paths := {"**/Dockerfile", "**/*.Dockerfile", "**/*.dockerfile"}
26
- required_hadolint_version := "v2.12.0"
27
- canonical_setup_bun_action := "./.github/actions/setup-bun-deps"
12
+ # Очікувані літерали з template.
13
+ expected_paths := {p | some p in data.template.snippet.on.push.paths}
14
+
15
+ # Required uses set — з template's steps.
16
+ expected_uses_set contains u if {
17
+ some step in data.template.snippet.jobs["lint-docker"].steps
18
+ u := object.get(step, "uses", "")
19
+ u != ""
20
+ }
28
21
 
29
- # Усі тексти `uses:` зі steps усіх jobs (incremental set rule per regal:
30
- # prefer-set-or-object-rule це краще за comprehension).
22
+ # Required run substrings collected per-step з template.
23
+ expected_run_substrings contains r if {
24
+ some step in data.template.snippet.jobs["lint-docker"].steps
25
+ r := object.get(step, "run", "")
26
+ r != ""
27
+ }
28
+
29
+ # Аліаси на input.
31
30
  all_step_uses contains u if {
32
31
  some job in object.get(input, "jobs", {})
33
32
  some step in object.get(job, "steps", [])
@@ -41,49 +40,36 @@ all_run_text := concat("\n", [run_text |
41
40
  run_text := step_run_to_text(step)
42
41
  ])
43
42
 
44
- # Множина значень `on.push.paths` (підтримує `on: { push: { paths: [...] } }`).
45
43
  push_paths_set := {p |
46
44
  some p in object.get(object.get(object.get(input, "on", {}), "push", {}), "paths", [])
47
45
  }
48
46
 
49
- # ── deny: on.push.paths ──────────────────────────────────────────────────
47
+ # ── deny: on.push.paths subset-of ──────────────────────────────────────
50
48
 
51
49
  deny contains msg if {
52
- some required in required_push_paths
50
+ some required in expected_paths
53
51
  not required in push_paths_set
54
52
  msg := sprintf("lint-docker.yml: on.push.paths має містити %q (docker.mdc)", [required])
55
53
  }
56
54
 
57
- # ── deny: hadolint install version ───────────────────────────────────────
58
-
59
- deny contains msg if {
60
- not contains(all_run_text, required_hadolint_version)
61
- msg := sprintf(
62
- "lint-docker.yml: крок hadolint install має містити версію %q (узгоджено з HADOLINT_IMAGE) (docker.mdc)",
63
- [required_hadolint_version],
64
- )
65
- }
66
-
67
- # ── deny: setup-bun-deps composite ───────────────────────────────────────
55
+ # ── deny: required uses present ────────────────────────────────────────
68
56
 
69
57
  deny contains msg if {
70
- not canonical_setup_bun_action in all_step_uses
71
- msg := concat(" ", [
72
- "lint-docker.yml: відсутній крок",
73
- "`uses: ./.github/actions/setup-bun-deps` (canonical composite per ga.mdc) (docker.mdc)",
74
- ])
58
+ some required_use in expected_uses_set
59
+ not required_use in all_step_uses
60
+ msg := sprintf("lint-docker.yml: відсутній крок `uses: %s` (docker.mdc)", [required_use])
75
61
  }
76
62
 
77
- # ── deny: bun run lint-docker ────────────────────────────────────────────
63
+ # ── deny: required run substrings ──────────────────────────────────────
78
64
 
79
65
  deny contains msg if {
80
- not contains(all_run_text, "bun run lint-docker")
81
- msg := "lint-docker.yml: жоден крок run не містить `bun run lint-docker` (docker.mdc)"
66
+ some required_run in expected_run_substrings
67
+ not contains(all_run_text, required_run)
68
+ msg := sprintf("lint-docker.yml: жоден крок run не містить %q (docker.mdc)", [required_run])
82
69
  }
83
70
 
84
- # ── helpers ──────────────────────────────────────────────────────────────
71
+ # ── helpers ────────────────────────────────────────────────────────────
85
72
 
86
- # Текст `run:` як один рядок: підтримує string і array форми (YAML).
87
73
  step_run_to_text(step) := step.run if is_string(step.run)
88
74
 
89
75
  else := concat("\n", [s | some s in step.run]) if is_array(step.run)
@@ -0,0 +1,41 @@
1
+ name: Lint Docker
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev
7
+ - main
8
+ paths:
9
+ - '**/Dockerfile'
10
+ - '**/*.Dockerfile'
11
+ - '**/*.dockerfile'
12
+
13
+ pull_request:
14
+ branches:
15
+ - dev
16
+ - main
17
+
18
+ concurrency:
19
+ group: ${{ github.ref }}-${{ github.workflow }}
20
+ cancel-in-progress: true
21
+
22
+ jobs:
23
+ lint-docker:
24
+ runs-on: ubuntu-latest
25
+ permissions:
26
+ contents: read
27
+ steps:
28
+ - uses: actions/checkout@v6
29
+ with:
30
+ persist-credentials: false
31
+
32
+ - name: Install hadolint
33
+ run: |
34
+ curl -sSL -o /tmp/hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64
35
+ chmod +x /tmp/hadolint
36
+ sudo mv /tmp/hadolint /usr/local/bin/hadolint
37
+
38
+ - uses: ./.github/actions/setup-bun-deps
39
+
40
+ - name: Lint Docker
41
+ run: bun run lint-docker
@@ -1,35 +1,18 @@
1
- # Перевірка `package.json` для docker (docker.mdc).
1
+ # Перевірка `package.json` (docker.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test package.json -p npm/policy/docker/package_json \
5
- # --namespace docker.package_json
6
- #
7
- # Canonical (docker.mdc): якщо у проєкті є правило `docker` (у `.n-cursor.json`),
8
- # у кореневому `package.json` має бути канонічний `scripts.lint-docker`.
9
- #
10
- # Цей пакет перевіряє ЛИШЕ зміст значення `scripts.lint-docker`, якщо ключ
11
- # присутній. Умовну обовʼязковість (правило `docker` у `.n-cursor.json` →
12
- # `scripts.lint-docker` ЗОБОВ'ЯЗАНИЙ існувати) перевіряє `check-bun.mjs` через
13
- # cross-file логіку (читає `.n-cursor.json` і `package.json`). Тут rego видно
14
- # лише один документ, тому без `.n-cursor.json`-контексту вимагати наявність
15
- # `scripts.lint-docker` означало б false-positive порушення для проєктів без docker.
16
- #
17
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Перевіряє ЛИШЕ зміст значення `scripts.lint-docker`, якщо ключ присутній.
5
+ # Умовну обовʼязковість (правило `docker` у `.n-cursor.json` → `scripts.lint-docker`
6
+ # зобовʼязаний існувати) перевіряє `check-bun.mjs` через cross-file логіку.
18
7
  package docker.package_json
19
8
 
20
9
  import rego.v1
21
10
 
22
- canonical_lint_docker := "n-cursor lint-docker"
23
-
24
- lint_docker_template := concat(" ", [
25
- "package.json: scripts.lint-docker має бути %q",
26
- "(зараз: %q) (docker.mdc)",
27
- ])
28
-
11
+ # Conditional snippet-check: тільки якщо значення непорожнє у input.
29
12
  deny contains msg if {
30
- scripts := object.get(input, "scripts", {})
31
- lint_docker := object.get(scripts, "lint-docker", "")
32
- lint_docker != ""
33
- trim_space(lint_docker) != canonical_lint_docker
34
- msg := sprintf(lint_docker_template, [canonical_lint_docker, lint_docker])
13
+ some script_name, expected in data.template.snippet.scripts
14
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
15
+ actual != ""
16
+ trim_space(actual) != expected
17
+ msg := sprintf("package.json: scripts.%s має бути %q (зараз: %q) (docker.mdc)", [script_name, expected, actual])
35
18
  }
@@ -0,0 +1 @@
1
+ { "scripts": { "lint-docker": "n-cursor lint-docker" } }
@@ -1,43 +1,41 @@
1
1
  # Структурна перевірка опт-аут конфігу для image-avif у `package.json` (image-avif.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test <pkg>/package.json -p npm/policy/image_avif/package_json \
5
- # --namespace image_avif.package_json
6
- #
7
- # Канонічна форма опт-ауту з image-avif.mdc:
8
- # { "@nitra/minify-image": { "disable-avif": true } }
9
- #
10
- # Поле опційне — більшість проєктів його не мають. Полісі deny лише, якщо поле
11
- # присутнє, але має нелегітимну форму: типовий typo (`disabled-avif`) або
12
- # неправильний тип (`"disable-avif": "yes"`). Без цієї перевірки помилкове
13
- # написання тихо повертає AVIF-генерацію всередину пакета, де її хотіли вимкнути.
3
+ # Канон надходить через --data: { "template": { "deny": ... } }
4
+ # Структура --data сформована з template/package.json.deny.json:
5
+ # `<field>.<typo_key>` — нелегітимні (typo) ключі під опт-аут конфігом.
14
6
  #
7
+ # Inverse type-перевірки (поле має бути обʼєктом, `disable-avif` — boolean) лишаються
8
+ # inline у rego, бо це не лежить на template-pattern (це інверс типу, не deny-key).
15
9
  # FS / behavior (запуск `npx @nitra/minify-image`, walk `.vue`, видалення AVIF-сиріт) — у JS.
16
- #
17
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
18
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
19
10
  package image_avif.package_json
20
11
 
21
12
  import rego.v1
22
13
 
23
- minify_image_field := "@nitra/minify-image"
14
+ # ── deny: typo-keys (template-driven) ────────────────────────────────────
15
+
16
+ deny contains msg if {
17
+ some field, typo_map in data.template.deny
18
+ cfg := object.get(input, field, null)
19
+ is_object(cfg)
20
+ some typo_key, reason in typo_map
21
+ typo_key in object.keys(cfg)
22
+ msg := sprintf("package.json: ключ \"%s.%s\" — %s", [field, typo_key, reason])
23
+ }
24
24
 
25
- # ── deny: значення поля має бути обʼєктом, якщо присутнє ──────────────────
25
+ # ── deny: тип поля (inverse лишається в rego) ──────────────────────────
26
26
 
27
27
  deny contains msg if {
28
- value := object.get(input, minify_image_field, null)
28
+ some field, _ in data.template.deny
29
+ value := object.get(input, field, null)
29
30
  value != null
30
31
  not is_object(value)
31
- msg := sprintf(
32
- "package.json: поле \"@nitra/minify-image\" має бути обʼєктом (зараз: %v) (image-avif.mdc)",
33
- [value],
34
- )
32
+ msg := sprintf("package.json: поле \"%s\" має бути обʼєктом (зараз: %v) (image-avif.mdc)", [field, value])
35
33
  }
36
34
 
37
- # ── deny: відомі ключі мають правильний тип ──────────────────────────────
35
+ # ── deny: тип disable-avif (inverse лишається в rego, hardcoded key) ──
38
36
 
39
37
  deny contains msg if {
40
- cfg := object.get(input, minify_image_field, {})
38
+ cfg := object.get(input, "@nitra/minify-image", {})
41
39
  is_object(cfg)
42
40
  value := object.get(cfg, "disable-avif", null)
43
41
  value != null
@@ -47,15 +45,3 @@ deny contains msg if {
47
45
  [value],
48
46
  )
49
47
  }
50
-
51
- # ── deny: захист від typo `disabled-avif` ────────────────────────────────
52
-
53
- deny contains msg if {
54
- cfg := object.get(input, minify_image_field, {})
55
- is_object(cfg)
56
- "disabled-avif" in object.keys(cfg)
57
- msg := concat(" ", [
58
- "package.json: ключ \"@nitra/minify-image.disabled-avif\" виглядає як typo —",
59
- "канонічна назва \"disable-avif\" (image-avif.mdc)",
60
- ])
61
- }
@@ -0,0 +1,5 @@
1
+ {
2
+ "@nitra/minify-image": {
3
+ "disabled-avif": "виглядає як typo — канонічна назва \"disable-avif\" (image-avif.mdc)"
4
+ }
5
+ }
@@ -11,14 +11,8 @@ CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (
11
11
 
12
12
  ## `package.json`
13
13
 
14
- ```json title="package.json"
15
- {
16
- "scripts": {
17
- "lint": "bun run lint-js && bun run lint-text && bun run lint-ga && bun run lint-image && oxfmt .",
18
- "lint-image": "npx @nitra/minify-image --src=. --write"
19
- }
20
- }
21
- ```
14
+ - Канон `lint-image` (substring requirements): [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
15
+ - Заборонені залежності `@nitra/minify-image` у deps/devDeps: [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
22
16
 
23
17
  Якщо в `package.json` уже є агрегований `lint`, додай у його ланцюжок `bun run lint-image` (як `bun run lint-text`, `bun run lint-js`, `bun run lint-ga`). Так розробник, що локально гонить `bun run lint`, перед фіксацією одразу бачить, чи зросли зображення.
24
18