@nitra/cursor 1.13.14 → 1.13.25

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 (71) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/package.json +1 -1
  3. package/rules/abie/abie.mdc +1 -4
  4. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
  5. package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
  6. package/rules/docker/docker.mdc +2 -50
  7. package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
  8. package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
  9. package/rules/docker/policy/package_json/package_json.rego +11 -28
  10. package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
  11. package/rules/image-avif/policy/package_json/package_json.rego +21 -35
  12. package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
  13. package/rules/image-compress/image-compress.mdc +2 -8
  14. package/rules/image-compress/policy/package_json/package_json.rego +24 -65
  15. package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
  16. package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
  17. package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
  18. package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
  19. package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
  20. package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
  21. package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
  22. package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
  23. package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
  24. package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
  25. package/rules/js-lint/policy/package_json/package_json.rego +22 -42
  26. package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
  27. package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  28. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
  29. package/rules/js-run/policy/configmap/configmap.rego +11 -35
  30. package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
  31. package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
  32. package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
  33. package/rules/js-run/policy/package_json/package_json.rego +10 -21
  34. package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
  35. package/rules/npm-module/npm-module.mdc +7 -36
  36. package/rules/npm-module/policy/emit_types_config/emit_types_config.rego +18 -27
  37. package/rules/npm-module/policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  38. package/rules/npm-module/policy/npm_package_json/npm_package_json.rego +20 -30
  39. package/rules/npm-module/policy/npm_package_json/template/package.json.snippet.json +1 -0
  40. package/rules/npm-module/policy/npm_publish_yml/npm_publish_yml.rego +46 -38
  41. package/rules/npm-module/policy/npm_publish_yml/template/npm-publish.yml.snippet.yml +34 -0
  42. package/rules/npm-module/policy/root_package_json/root_package_json.rego +17 -17
  43. package/rules/npm-module/policy/root_package_json/template/package.json.snippet.json +1 -0
  44. package/rules/php/php.mdc +2 -56
  45. package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
  46. package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
  47. package/rules/php/policy/package_json/package_json.rego +9 -12
  48. package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
  49. package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
  50. package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
  51. package/rules/style-lint/policy/package_json/package_json.rego +22 -30
  52. package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
  53. package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
  54. package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  55. package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
  56. package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
  57. package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
  58. package/rules/text/policy/cspell/cspell.rego +39 -59
  59. package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
  60. package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
  61. package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
  62. package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
  63. package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
  64. package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
  65. package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
  66. package/rules/text/policy/package_json/package_json.rego +14 -52
  67. package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
  68. package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  69. package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
  70. package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
  71. package/rules/text/policy/vscode_settings/vscode_settings.rego +20 -42
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.25] - 2026-05-17
8
+
9
+ ### Added
10
+
11
+ - `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).
12
+
13
+ ### Closed
14
+
15
+ - 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.
16
+
17
+ ## [1.13.24] - 2026-05-17
18
+
19
+ ### Added
20
+
21
+ - `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).
22
+
23
+ ## [1.13.23] - 2026-05-17
24
+
25
+ ### Added
26
+
27
+ - `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.
28
+
29
+ ## [1.13.22] - 2026-05-17
30
+
31
+ ### Added
32
+
33
+ - `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.
34
+
35
+ ## [1.13.21] - 2026-05-17
36
+
37
+ ### Added
38
+
39
+ - `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`).
40
+ - Drift-тести у кожному `*_test.rego`.
41
+
42
+ ## [1.13.20] - 2026-05-17
43
+
44
+ ### Added
45
+
46
+ - `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.
47
+ - `abie.mdc` — inline `ignore_branches` фрагмент замінено на template-link.
48
+
49
+ ## [1.13.19] - 2026-05-17
50
+
51
+ ### Added
52
+
53
+ - `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).
54
+ - `docker.mdc` — 2 inline-блоки замінено на template-links.
55
+
56
+ ## [1.13.18] - 2026-05-17
57
+
58
+ ### Added
59
+
60
+ - `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 тестів).
61
+
62
+ ## [1.13.17] - 2026-05-17
63
+
64
+ ### Added
65
+
66
+ - `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 тестів).
67
+ - `php.mdc` — 2 inline-блоки замінено на template-links.
68
+
69
+ ## [1.13.16] - 2026-05-17
70
+
71
+ ### Added
72
+
73
+ - `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.
74
+ - `image-compress.mdc` — inline `package.json` snippet замінено на template-links.
75
+
76
+ ## [1.13.15] - 2026-05-17
77
+
78
+ ### Added
79
+
80
+ - `npm-module` rule template/ міграція (Phase 5): усі 4 policy концерни — `emit_types_config` (fragment, 2-level walker), `root_package_json` (fragment, snippet-array `workspaces`), `npm_package_json` (partial — `files` whitelist у template, regex `types` + `devDependencies`-must-be-empty лишаються у rego), `npm_publish_yml` (full-canon workflow з per-concern field-by-field rego).
81
+ - 3 нові `*_test.rego` (раніше тестів не було для `emit_types_config`, `root_package_json`, `npm_publish_yml`) — кожен покриває canonical + 4-6 негативних + drift.
82
+
83
+ ### Changed
84
+
85
+ - `emit_types_config.rego` — generic 2-level snippet walker (як `bun.bunfig` / `ga.vscode_settings`).
86
+ - `root_package_json.rego` — generic snippet-array subset-of walker (під `workspaces` і потенційні наступні масиви).
87
+ - `npm_package_json.rego` — `files` whitelist тепер subset-of через `data.template.snippet`; `types` regex + `devDependencies`-must-be-empty лишаються у rego як inverse-patterns.
88
+ - `npm_publish_yml.rego` — повний канон workflow у `template/npm-publish.yml.snippet.yml` (як ga workflow concerns), expected paths/branches/permissions/uses-marker читаються з `data.template.snippet.<path>`.
89
+ - `npm-module.mdc` — inline `npm-publish.yml` блок (35 рядків) замінено на template-link; додано окрему секцію «Канонічні конфіги» з лінками на `root_package_json`, `npm_package_json`, `emit_types_config` template-файли.
90
+ - `docs/adr/template-dir-concern-inventory.md` — усі 4 `npm-module.*` концерни позначено ✓; додано Phase 5 у прогрес; tally: 19/39 (49%).
91
+
7
92
  ## [1.13.14] - 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.14",
3
+ "version": "1.13.25",
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
@@ -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