@nitra/cursor 1.13.15 → 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.
- package/CHANGELOG.md +69 -0
- package/package.json +1 -1
- package/rules/abie/abie.mdc +1 -4
- package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
- package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
- package/rules/docker/docker.mdc +2 -50
- package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
- package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
- package/rules/docker/policy/package_json/package_json.rego +11 -28
- package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
- package/rules/image-avif/policy/package_json/package_json.rego +21 -35
- package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
- package/rules/image-compress/image-compress.mdc +2 -8
- package/rules/image-compress/policy/package_json/package_json.rego +24 -65
- package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
- package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
- package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
- package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
- package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
- package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
- package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
- package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
- package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
- package/rules/js-lint/policy/package_json/package_json.rego +22 -42
- package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
- package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
- package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
- package/rules/js-run/policy/configmap/configmap.rego +11 -35
- package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
- package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/policy/package_json/package_json.rego +10 -21
- package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
- package/rules/php/php.mdc +2 -56
- package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
- package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
- package/rules/php/policy/package_json/package_json.rego +9 -12
- package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
- package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
- package/rules/style-lint/policy/package_json/package_json.rego +22 -30
- package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
- package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
- package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
- package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
- package/rules/text/policy/cspell/cspell.rego +39 -59
- package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
- package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
- package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
- package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
- package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
- package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
- package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
- package/rules/text/policy/package_json/package_json.rego +14 -52
- package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
- package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
- package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
- package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
- package/rules/text/policy/vscode_settings/vscode_settings.rego +20 -42
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,75 @@
|
|
|
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
|
+
|
|
7
76
|
## [1.13.15] - 2026-05-17
|
|
8
77
|
|
|
9
78
|
### Added
|
package/package.json
CHANGED
package/rules/abie/abie.mdc
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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
|
-
#
|
|
26
|
-
|
|
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
|
-
|
|
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 :=
|
|
32
|
-
"clean-merged-branch.yml: не знайдено крок з uses:
|
|
33
|
-
|
|
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 :=
|
|
37
|
-
"clean-merged-branch.yml: не знайдено with.ignore_branches у кроці",
|
|
38
|
-
|
|
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) != ""
|
package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml
ADDED
|
@@ -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/docker/docker.mdc
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
#
|
|
30
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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:
|
|
63
|
+
# ── deny: required run substrings ──────────────────────────────────────
|
|
78
64
|
|
|
79
65
|
deny contains msg if {
|
|
80
|
-
|
|
81
|
-
|
|
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`
|
|
1
|
+
# Перевірка `package.json` (docker.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
trim_space(
|
|
34
|
-
msg := sprintf(
|
|
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
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
}
|
|
@@ -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
|
-
|
|
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
|
|