@nitra/cursor 12.8.6 → 12.8.8

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 (263) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/rules/abie/main.mdc +9 -5
  4. package/rules/abie/policy/base_deployment_preem/base_deployment_preem.mdc +22 -0
  5. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +19 -0
  6. package/rules/abie/policy/health_check_policy/health_check_policy.mdc +17 -0
  7. package/rules/abie/policy/http_route_base/http_route_base.mdc +9 -0
  8. package/rules/abie/policy/package_json_shared/package_json_shared.mdc +17 -0
  9. package/rules/adr/js/hooks.mdc +32 -0
  10. package/rules/adr/js/madr_format.mdc +96 -0
  11. package/rules/adr/js/settings_policy.mdc +34 -0
  12. package/rules/adr/main.mdc +17 -95
  13. package/rules/adr/policy/settings_json/settings_json.mdc +7 -0
  14. package/rules/adr/policy/settings_local_json/settings_local_json.mdc +7 -0
  15. package/rules/bun/js/bunfig.mdc +12 -0
  16. package/rules/bun/js/layout.mdc +60 -0
  17. package/rules/bun/js/lint.mdc +9 -0
  18. package/rules/bun/js/package_json.mdc +19 -0
  19. package/rules/bun/main.mdc +7 -60
  20. package/rules/bun/policy/bunfig/bunfig.mdc +12 -0
  21. package/rules/bun/policy/package_json/package_json.mdc +14 -0
  22. package/rules/capacitor/js/ios_spm.mdc +69 -0
  23. package/rules/capacitor/js/version.mdc +29 -0
  24. package/rules/capacitor/main.mdc +6 -22
  25. package/rules/capacitor/policy/package_json/package_json.mdc +9 -0
  26. package/rules/changelog/js/agent-workflow.mdc +15 -0
  27. package/rules/changelog/js/changelog-format.mdc +33 -0
  28. package/rules/changelog/js/comparison-models.mdc +40 -0
  29. package/rules/changelog/main.mdc +4 -98
  30. package/rules/ci4/js/marksman_config.mdc +31 -0
  31. package/rules/ci4/js/vscode_extensions.mdc +33 -0
  32. package/rules/ci4/main.mdc +16 -14
  33. package/rules/ci4/policy/vscode_extensions/vscode_extensions.mdc +9 -0
  34. package/rules/docker/js/compile.mdc +44 -0
  35. package/rules/docker/js/hadolint.mdc +50 -0
  36. package/rules/docker/js/mirror.mdc +13 -0
  37. package/rules/docker/js/multistage.mdc +13 -0
  38. package/rules/docker/js/native-addon.mdc +43 -0
  39. package/rules/docker/js/nginx-tag.mdc +7 -0
  40. package/rules/docker/js/nginx-user.mdc +37 -0
  41. package/rules/docker/js/non-root.mdc +39 -0
  42. package/rules/docker/main.mdc +13 -196
  43. package/rules/docker/policy/lint_docker_yml/lint_docker_yml.mdc +14 -0
  44. package/rules/efes/main.mdc +1 -1
  45. package/rules/efes/policy/package_json_shared/package_json_shared.mdc +30 -0
  46. package/rules/ga/js/lint_toolchain.mdc +15 -0
  47. package/rules/ga/js/required_workflows.mdc +35 -0
  48. package/rules/ga/js/vscode.mdc +17 -0
  49. package/rules/ga/js/workflow_common.mdc +108 -0
  50. package/rules/ga/js/workflows.mdc +32 -0
  51. package/rules/ga/js/zizmor.mdc +7 -0
  52. package/rules/ga/main.mdc +16 -119
  53. package/rules/ga/policy/clean_ga_workflows/clean_ga_workflows.mdc +18 -0
  54. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.mdc +22 -0
  55. package/rules/ga/policy/git_ai/git_ai.mdc +19 -0
  56. package/rules/ga/policy/lint_ga/lint_ga.mdc +21 -0
  57. package/rules/ga/policy/vscode_extensions/vscode_extensions.mdc +9 -0
  58. package/rules/ga/policy/vscode_settings/vscode_settings.mdc +9 -0
  59. package/rules/ga/policy/workflow_common/workflow_common.mdc +18 -0
  60. package/rules/ga/policy/zizmor_yml/zizmor_yml.mdc +9 -0
  61. package/rules/graphql/js/tooling.mdc +13 -0
  62. package/rules/graphql/js/vscode_extensions.mdc +13 -0
  63. package/rules/graphql/main.mdc +4 -21
  64. package/rules/graphql/policy/vscode_extensions/vscode_extensions.mdc +9 -0
  65. package/rules/hasura/js/internal_urls.mdc +27 -0
  66. package/rules/hasura/js/migrations.mdc +13 -0
  67. package/rules/hasura/js/svc_hl.mdc +17 -0
  68. package/rules/hasura/main.mdc +6 -30
  69. package/rules/hasura/policy/svc_hl/svc_hl.mdc +15 -0
  70. package/rules/image-avif/js/avif_generation.mdc +26 -0
  71. package/rules/image-avif/js/package_json_optout.mdc +21 -0
  72. package/rules/image-avif/main.mdc +5 -34
  73. package/rules/image-avif/policy/package_json/package_json.mdc +18 -0
  74. package/rules/image-compress/js/package_json.mdc +7 -0
  75. package/rules/image-compress/js/package_setup.mdc +13 -0
  76. package/rules/image-compress/main.mdc +4 -12
  77. package/rules/image-compress/policy/package_json/package_json.mdc +13 -0
  78. package/rules/js/docs/index.md +3 -3
  79. package/rules/js/js/dep-policy.mdc +17 -0
  80. package/rules/js/js/eslint-config.mdc +28 -0
  81. package/rules/js/js/extensions.mdc +8 -0
  82. package/rules/js/js/file-extensions.mdc +12 -0
  83. package/rules/js/js/for-in.mdc +26 -0
  84. package/rules/js/js/jscpd.mdc +42 -0
  85. package/rules/js/js/knip.mdc +15 -0
  86. package/rules/js/js/lint-js-workflow.mdc +58 -0
  87. package/rules/js/js/oxlintrc.mdc +20 -0
  88. package/rules/js/js/package-json.mdc +31 -0
  89. package/rules/js/js/tests.mdc +9 -0
  90. package/rules/js/js/utils-lib-structure.mdc +15 -0
  91. package/rules/js/main.mdc +19 -211
  92. package/rules/js/policy/jscpd/jscpd.mdc +14 -0
  93. package/rules/js/policy/lint_js_yml/lint_js_yml.mdc +14 -0
  94. package/rules/js/policy/package_json/package_json.mdc +15 -0
  95. package/rules/js/policy/vscode_extensions/vscode_extensions.mdc +11 -0
  96. package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
  97. package/rules/js-bun-db/js/connection.mdc +42 -0
  98. package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
  99. package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
  100. package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
  101. package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
  102. package/rules/js-bun-db/js/query-safety.mdc +117 -0
  103. package/rules/js-bun-db/js/sql-array.mdc +88 -0
  104. package/rules/js-bun-db/js/unsafe.mdc +65 -0
  105. package/rules/js-bun-db/main.mdc +12 -607
  106. package/rules/js-bun-db/policy/package_json/package_json.mdc +17 -0
  107. package/rules/js-bun-redis/js/imports.mdc +47 -0
  108. package/rules/js-bun-redis/js/package_json.mdc +44 -0
  109. package/rules/js-bun-redis/main.mdc +4 -10
  110. package/rules/js-bun-redis/policy/package_json/package_json.mdc +11 -0
  111. package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
  112. package/rules/js-mssql/js/mssql-pool.mdc +56 -0
  113. package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
  114. package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
  115. package/rules/js-mssql/js/mssql-version.mdc +7 -0
  116. package/rules/js-mssql/main.mdc +10 -198
  117. package/rules/js-mssql/policy/package_json/package_json.mdc +9 -0
  118. package/rules/js-run/js/check-env.mdc +35 -0
  119. package/rules/js-run/js/conn-aliases.mdc +109 -0
  120. package/rules/js-run/js/jsconfig.mdc +20 -0
  121. package/rules/js-run/js/otel-configmap.mdc +6 -0
  122. package/rules/js-run/js/pino.mdc +6 -0
  123. package/rules/js-run/js/project-structure.mdc +11 -0
  124. package/rules/js-run/js/runtime.mdc +14 -0
  125. package/rules/js-run/js/scope.mdc +11 -0
  126. package/rules/js-run/js/settimeout.mdc +11 -0
  127. package/rules/js-run/js/temporal.mdc +5 -0
  128. package/rules/js-run/main.mdc +16 -216
  129. package/rules/js-run/policy/configmap/configmap.mdc +31 -0
  130. package/rules/js-run/policy/jsconfig/jsconfig.mdc +25 -0
  131. package/rules/js-run/policy/package_json/package_json.mdc +38 -0
  132. package/rules/k8s/js/configmap.mdc +41 -0
  133. package/rules/k8s/js/deployment_resources.mdc +49 -0
  134. package/rules/k8s/js/hasura_httproute.mdc +91 -0
  135. package/rules/k8s/js/hpa_apiversion.mdc +27 -0
  136. package/rules/k8s/js/ingress_gateway.mdc +16 -0
  137. package/rules/k8s/js/kustomize_structure.mdc +144 -0
  138. package/rules/k8s/js/lint_k8s.mdc +72 -0
  139. package/rules/k8s/js/multidoc_yaml.mdc +5 -0
  140. package/rules/k8s/js/network_policy.mdc +136 -0
  141. package/rules/k8s/js/schema_modeline.mdc +57 -0
  142. package/rules/k8s/js/service.mdc +44 -0
  143. package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
  144. package/rules/k8s/main.mdc +29 -834
  145. package/rules/k8s/policy/base_kustomization/base_kustomization.mdc +12 -0
  146. package/rules/k8s/policy/base_manifest/base_manifest.mdc +14 -0
  147. package/rules/k8s/policy/gateway/gateway.mdc +17 -0
  148. package/rules/k8s/policy/hasura_configmap/hasura_configmap.mdc +20 -0
  149. package/rules/k8s/policy/hasura_httproute/hasura_httproute.mdc +16 -0
  150. package/rules/k8s/policy/hpa_pdb/hpa_pdb.mdc +23 -0
  151. package/rules/k8s/policy/kustomization/kustomization.mdc +20 -0
  152. package/rules/k8s/policy/manifest/manifest.mdc +17 -0
  153. package/rules/k8s/policy/network_policy/network_policy.mdc +22 -0
  154. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml.mdc +13 -0
  155. package/rules/k8s/policy/svc_yaml/svc_yaml.mdc +12 -0
  156. package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
  157. package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
  158. package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
  159. package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
  160. package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
  161. package/rules/nginx-default-tpl/main.mdc +8 -110
  162. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.mdc +11 -0
  163. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.mdc +15 -0
  164. package/rules/npm-module/js/docs/index.md +5 -5
  165. package/rules/npm-module/js/docs/rule_meta.md +6 -6
  166. package/rules/npm-module/js/docs/skill_meta.md +8 -8
  167. package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
  168. package/rules/npm-module/js/package_structure.mdc +62 -0
  169. package/rules/npm-module/js/rule_meta.mdc +11 -0
  170. package/rules/npm-module/js/skill_meta.mdc +11 -0
  171. package/rules/npm-module/main.mdc +10 -52
  172. package/rules/npm-module/policy/emit_types_config/emit_types_config.mdc +40 -0
  173. package/rules/npm-module/policy/npm_package_json/npm_package_json.mdc +50 -0
  174. package/rules/npm-module/policy/root_package_json/root_package_json.mdc +37 -0
  175. package/rules/php/js/lint_php_yml.mdc +12 -0
  176. package/rules/php/js/tooling.mdc +66 -0
  177. package/rules/php/main.mdc +5 -66
  178. package/rules/php/policy/lint_php_yml/lint_php_yml.mdc +21 -0
  179. package/rules/python/js/lint_python_yml.mdc +23 -0
  180. package/rules/python/js/pyproject_toml.mdc +32 -0
  181. package/rules/python/js/tooling.mdc +23 -0
  182. package/rules/python/main.mdc +7 -32
  183. package/rules/python/policy/lint_python_yml/lint_python_yml.mdc +12 -0
  184. package/rules/python/policy/pyproject_toml/pyproject_toml.mdc +13 -0
  185. package/rules/rego/js/rego-lint.mdc +31 -0
  186. package/rules/rego/js/vscode_extensions.mdc +11 -0
  187. package/rules/rego/js/vscode_settings.mdc +13 -0
  188. package/rules/rego/main.mdc +10 -22
  189. package/rules/rego/policy/vscode_extensions/vscode_extensions.mdc +11 -0
  190. package/rules/rego/policy/vscode_settings/vscode_settings.mdc +19 -0
  191. package/rules/rust/js/coverage.mdc +28 -0
  192. package/rules/rust/js/lint.mdc +22 -0
  193. package/rules/rust/js/tauri_composition.mdc +8 -0
  194. package/rules/rust/js/vscode_extensions.mdc +12 -0
  195. package/rules/rust/main.mdc +8 -38
  196. package/rules/rust/policy/lint_rust_yml/lint_rust_yml.mdc +12 -0
  197. package/rules/rust/policy/vscode_extensions/vscode_extensions.mdc +9 -0
  198. package/rules/security/js/rego_policies.mdc +15 -0
  199. package/rules/security/js/sample_secret.mdc +19 -0
  200. package/rules/security/js/trufflehog.mdc +21 -0
  201. package/rules/security/main.mdc +7 -34
  202. package/rules/security/policy/lint_security_yml/lint_security_yml.mdc +7 -0
  203. package/rules/security/policy/package_json/package_json.mdc +7 -0
  204. package/rules/style/js/admin-table.mdc +88 -0
  205. package/rules/style/js/colors.mdc +21 -0
  206. package/rules/style/js/gap.mdc +22 -0
  207. package/rules/style/js/quasar-fixes.mdc +32 -0
  208. package/rules/style/js/quasar.mdc +7 -0
  209. package/rules/style/js/tooling.mdc +85 -0
  210. package/rules/style/main.mdc +12 -251
  211. package/rules/style/policy/lint_style_yml/lint_style_yml.mdc +13 -0
  212. package/rules/style/policy/package_json/package_json.mdc +18 -0
  213. package/rules/style/policy/vscode_extensions/vscode_extensions.mdc +13 -0
  214. package/rules/style/policy/vscode_settings/vscode_settings.mdc +19 -0
  215. package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
  216. package/rules/tauri/js/tool_surface.mdc +21 -0
  217. package/rules/tauri/js/tooling.mdc +25 -0
  218. package/rules/tauri/main.mdc +6 -78
  219. package/rules/tauri/policy/vscode_extensions/vscode_extensions.mdc +21 -0
  220. package/rules/test/js/cargo_mutants_config.mdc +18 -0
  221. package/rules/test/js/docs/index.md +7 -7
  222. package/rules/test/js/location.mdc +52 -0
  223. package/rules/test/js/no-console-store-restore.mdc +11 -0
  224. package/rules/test/js/no-process-chdir.mdc +15 -0
  225. package/rules/test/js/no-relative-fs-path.mdc +22 -0
  226. package/rules/test/js/sandbox-aware-test.mdc +28 -0
  227. package/rules/test/js/stryker_config.mdc +26 -0
  228. package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
  229. package/rules/test/main.mdc +16 -184
  230. package/rules/test/policy/package_json/package_json.mdc +16 -0
  231. package/rules/text/js/ci-lint-text.mdc +15 -0
  232. package/rules/text/js/cspell.mdc +81 -0
  233. package/rules/text/js/dotenv-linter.mdc +16 -0
  234. package/rules/text/js/forbidden-prettier.mdc +13 -0
  235. package/rules/text/js/markdownlint.mdc +25 -0
  236. package/rules/text/js/oxfmt.mdc +35 -0
  237. package/rules/text/js/package-json.mdc +26 -0
  238. package/rules/text/js/shellcheck.mdc +18 -0
  239. package/rules/text/js/v8r.mdc +23 -0
  240. package/rules/text/js/vscode.mdc +86 -0
  241. package/rules/text/main.mdc +20 -231
  242. package/rules/text/policy/cspell/cspell.mdc +34 -0
  243. package/rules/text/policy/lint_text/lint_text.mdc +19 -0
  244. package/rules/text/policy/markdownlint/markdownlint.mdc +38 -0
  245. package/rules/text/policy/oxfmtrc/oxfmtrc.mdc +11 -0
  246. package/rules/text/policy/package_json/package_json.mdc +33 -0
  247. package/rules/text/policy/vscode_extensions/vscode_extensions.mdc +13 -0
  248. package/rules/text/policy/vscode_settings/vscode_settings.mdc +13 -0
  249. package/rules/vue/js/composition-api.mdc +82 -0
  250. package/rules/vue/js/nheader-layout.mdc +171 -0
  251. package/rules/vue/js/node-imports.mdc +25 -0
  252. package/rules/vue/js/quasar-ui.mdc +32 -0
  253. package/rules/vue/js/structure.mdc +101 -0
  254. package/rules/vue/js/testing.mdc +32 -0
  255. package/rules/vue/js/tfm-translations.mdc +26 -0
  256. package/rules/vue/js/vite-config.mdc +126 -0
  257. package/rules/vue/js/vite-env.mdc +55 -0
  258. package/rules/vue/js/vue-imports.mdc +25 -0
  259. package/rules/vue/main.mdc +15 -641
  260. package/rules/vue/policy/package_json/package_json.mdc +30 -0
  261. package/scripts/docs/index.md +16 -16
  262. package/scripts/lib/docs/index.md +36 -36
  263. package/scripts/utils/docs/index.md +14 -14
@@ -0,0 +1,44 @@
1
+ ## Компіляція bun-проєкту в бінарник
2
+
3
+ Якщо проект має `bun install` крок, та не є фронтенд проектом (тобто не має `bun build` крок без `--compile`), **і не має нативного `.node`-аддона з динамічним завантаженням** (див. `native-addon.mdc`), то потрібно щоб була компіляція коду, і далі у фінальному образі був тільки бінарник. Цей образ не містить компілятора, npm, Bun — тільки runtime libs.
4
+
5
+ Тригер перевірки:
6
+ - у Dockerfile є крок `bun install` (або `bun i`);
7
+ - фінальний FROM — `mirror.gcr.io/library/alpine:*` (тобто не nginx/openresty frontend).
8
+
9
+ Очікування:
10
+ - у build stage є `bun build --compile`;
11
+ - у фінальному stage немає викликів `bun` (залишків build tooling).
12
+
13
+ ### Канон — компільований бінарник на alpine
14
+
15
+ ```dockerfile
16
+ FROM mirror.gcr.io/oven/bun:alpine AS build-env
17
+
18
+ WORKDIR /app
19
+
20
+ ENV NODE_ENV=production
21
+
22
+ COPY package.json .
23
+ COPY bunfig.toml .
24
+
25
+ RUN bun install --production
26
+
27
+ COPY ./src ./src
28
+
29
+ # Компілюємо в бінарник
30
+ RUN bun build --compile --outfile app ./src/index.js
31
+
32
+ FROM mirror.gcr.io/library/alpine:latest
33
+
34
+ # (libstdc++ libgcc) для Bun runtime, (tzdata) для часового поясу
35
+ RUN apk add --no-cache libstdc++ libgcc tzdata
36
+
37
+ WORKDIR /app
38
+
39
+ COPY --from=build-env /app/app ./app
40
+
41
+ CMD ["./app"]
42
+ ```
43
+
44
+ Перевіряє `getBunCompileHint` у **`npm/rules/docker/js/lint.mjs`**.
@@ -0,0 +1,50 @@
1
+ ## lint-docker: hadolint і CI-workflow
2
+
3
+ ### Область lint-docker
4
+
5
+ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить правило **`n-cursor lint docker`** (реалізація — **`npm/rules/docker/js/lint.mjs`**).
6
+
7
+ **Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`rules/docker/fix.mjs`).
8
+
9
+ Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`rules/docker/fix.mjs`**. Виклик **`hadolint`** як **нативного бінарника** через **`ensureTool`** (PATH → кеш → авто-install brew/scoop/GitHub Release; **без** `docker run`) — спільна логіка **`npm/rules/docker/lib/docker-hadolint.mjs`**.
10
+
11
+ ### GitHub Actions workflow
12
+
13
+ Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
14
+
15
+ - Канон: [lint-docker.yml.snippet.yml](./policy/lint_docker_yml/template/lint-docker.yml.snippet.yml)
16
+
17
+ Локально hadolint авто-встановлюється через **`ensureTool`** (latest, без піну версії). У CI встанови його кроком із workflow-сніпета (curl-download бінарника — без `docker run`).
18
+
19
+ ### Конфігурація hadolint
20
+
21
+ Кореневий **`.hadolint.yaml`**: вимкнення правил, trusted registries — [документація](https://github.com/hadolint/hadolint#configure). Щоб не додавати **`# hadolint ignore=DL3007`** у кожному **`FROM`** з **`:latest`**, у корені репозиторію задати глобально:
22
+
23
+ ```yaml title=".hadolint.yaml"
24
+ ignored:
25
+ - DL3007
26
+ - DL3008
27
+ - DL3018
28
+ ```
29
+
30
+ Де DL3007 — «Не використовуй тег latest у FROM»
31
+ Де DL3018 — «Піни версії пакетів у apk add»
32
+
33
+ ### Запуск
34
+
35
+ 1. **`n-cursor lint docker`** — **`js/lint.mjs`**: **`Dockerfile`** та **`*.Dockerfile`** (lint-docker); у CI використовуй **`n-cursor lint docker --read-only`** і встанови hadolint (приклад у workflow).
36
+ 2. **`npx @nitra/cursor fix docker`** — **`rules/docker/fix.mjs`**, виклик hadolint як у **`docker-hadolint.mjs`** (нативний бінарник через **`ensureTool`**; **без** `docker run`).
37
+
38
+ Окремий `package.json`-скрипт `lint-docker` не потрібен і не перевіряється — єдина точка входу для правила: **`n-cursor lint docker`**.
39
+
40
+ Якщо немає файлів у межах відповідного набору (**`lint-docker`** або **`check docker`**) — перевірка пропускається (exit 0).
41
+
42
+ Винятки: **`# hadolint ignore=DL3008`** (або інший код) у Dockerfile або **`ignored`** у **`.hadolint.yaml`** (наприклад **DL3007** для **`:latest`** — div. вище).
43
+
44
+ ### Rego-перевірка (conftest)
45
+
46
+ Пакет `docker.lint_docker_yml` перевіряє `.github/workflows/lint-docker.yml` на відповідність канонічному сніпету:
47
+
48
+ - `on.push.paths` містить обов'язкові шляхи з template;
49
+ - присутні всі `uses:` кроки з template;
50
+ - всі `run:` підрядки з template є у workflow.
@@ -0,0 +1,13 @@
1
+ ## GCR-дзеркало замість Docker Hub
2
+
3
+ Для образів з Docker Hub — **`oven/bun`**, **`alpine`**, **`nginx`**, **`nginxinc/nginx-unprivileged`**, **`node`** — у **`FROM`** треба вказувати дзеркало GCR, а не pull напряму з Hub:
4
+
5
+ | Docker Hub | GCR-дзеркало |
6
+ |---|---|
7
+ | `oven/bun` | `mirror.gcr.io/oven/bun` |
8
+ | `alpine` | `mirror.gcr.io/library/alpine` |
9
+ | `nginx` | `mirror.gcr.io/library/nginx` |
10
+ | `node` | `mirror.gcr.io/library/node` |
11
+ | `nginxinc/nginx-unprivileged` | `mirror.gcr.io/nginxinc/nginx-unprivileged` |
12
+
13
+ Перевіряє **`npm/rules/docker/lib/docker-mirror.mjs`** (через `getMirrorGcrHint`); точка входу обходу — **`npm/rules/docker/fix.mjs`** та **`npm/rules/docker/js/lint.mjs`**.
@@ -0,0 +1,13 @@
1
+ ## Multistage build і дозволені runtime-образи
2
+
3
+ Dockerfile/Containerfile **має бути multistage build**: окремий build stage (залежності/компіляція) і окремий runtime stage. У фінальному stage дозволені лише мінімальні базові образи:
4
+
5
+ - **backend (типово)**: `mirror.gcr.io/library/alpine:*`
6
+ - **ультра-легкі (glibc / одна статична збірка)**: `scratch` — тільки як `FROM scratch` (офіційний порожній базовий шар), коли весь **runtime** уже в `COPY --from=…`
7
+ - **glibc, Debian (slim)**: `mirror.gcr.io/library/debian:*` **лише** з тегом, у якому є `slim` (наприклад `bookworm-slim`, `trixie-slim`), а не `bookworm` без `slim`. Debian-slim виправданий **лише** коли потрібен саме glibc-рантайм (нативні glibc-залежності, prebuilds без musl-варіанта). **Non-root сам по собі не є підставою** переходити сюди: `mirror.gcr.io/oven/bun:alpine` уже має користувача `bun` (uid/gid 1000), тож `USER bun` дає non-root **без зміни бази**. Перехід Alpine→Debian заради лише non-root — **антипатерн** (більший образ + зайва musl→glibc міграція bun-бінарника)
8
+ - **виняток (інтерпретовані стеки)**: `mirror.gcr.io/library/php:*` або `mirror.gcr.io/library/python:*` — якщо сервіс має крутитися в офіційному runtime PHP чи Python, а не як один бінарник на Alpine; інакше лишай **alpine** у фінальному stage
9
+ - **frontend**: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` або `mirror.gcr.io/openresty/openresty:*`
10
+
11
+ Це стримує зайвий build tooling (Bun, **node_modules** зі збірки) у фінальному образі; для **alpine** / **nginx** / **openresty** у **runtime** лишаються лише відповідні вимоги, для **php** / **python** (виняток) — цільовий інтерпретований **stack**; **scratch** і **debian** з тегом **`*slim*`** — коли glibc і мінімальне оточення Debian важливіші за musl в **alpine**.
12
+
13
+ Перевіряє `getMultistageAndRuntimeHint` у **`npm/rules/docker/js/lint.mjs`**.
@@ -0,0 +1,43 @@
1
+ ## Виняток: нативний `.node`-аддон (sharp / @img/* / argon2)
2
+
3
+ Якщо в `package.json#dependencies` є нативний `.node`-аддон, який вантажиться через **динамічний `require`** — передусім **`sharp`**; той самий клас — **`@img/*`**, **`argon2`** — його **не можна** пакувати через `bun build --compile`.
4
+
5
+ `bun build --compile` не трейсить `require(\`@img/sharp-${platform}/sharp.node\`)` і не вшиває нативний біндинг → компільований бінарник падає в рантаймі: `Could not load the "sharp" module using the linuxmusl-arm64 runtime`. Доведено реальними docker-збірками (bun 1.3.14, sharp 0.34.5); відтворюється і на darwin-arm64 (тобто не musl/glibc-залежне). `apk add vips` **НЕ лікує** — він дає системний libvips, а бракує саме `sharp.node`.
6
+
7
+ Канон — ship `node_modules` і запускати через `bun <entry>` на базі `mirror.gcr.io/oven/bun:alpine` (це легітимний виняток до правила «лише alpine/scratch у фінальному stage» — тут потрібен саме bun-рантайм). База `oven/bun` уже має non-root користувача `bun` (uid/gid 1000). Entry бери з наявного `--outfile`-таргета / `package.json#main` / `scripts.start`; якщо не визначити — лиши TODO-маркер, не вгадуй.
8
+
9
+ Список нативних аддонів — розширювана константа `NATIVE_ADDON_PACKAGES` / `NATIVE_ADDON_SCOPES` у **`npm/rules/docker/lib/docker-native-addon.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
10
+
11
+ ### Антипатерн (це правило ловить)
12
+
13
+ ```dockerfile
14
+ RUN bun build --compile --outfile app ./src/index.js # ← з sharp у deps
15
+ FROM mirror.gcr.io/library/alpine:latest
16
+ RUN apk add --no-cache ... vips # системний vips не рятує
17
+ COPY --from=build-env --chown=app:app /app/app ./app
18
+ USER app
19
+ CMD ["./app"]
20
+ ```
21
+
22
+ ### Канон (привести до цього)
23
+
24
+ ```dockerfile
25
+ FROM mirror.gcr.io/oven/bun:alpine AS build-env
26
+ WORKDIR /app
27
+ ENV NODE_ENV=production
28
+ COPY package.json .
29
+ RUN bun install --production
30
+ COPY ./src ./src
31
+
32
+ FROM mirror.gcr.io/oven/bun:alpine
33
+ RUN apk add --no-cache tzdata
34
+ WORKDIR /app
35
+ # база oven/bun має non-root користувача bun (uid/gid 1000)
36
+ COPY --from=build-env --chown=bun:bun /app/node_modules ./node_modules
37
+ COPY --from=build-env --chown=bun:bun /app/src ./src
38
+ COPY --from=build-env --chown=bun:bun /app/package.json ./package.json
39
+ USER bun
40
+ CMD ["bun", "src/index.js"]
41
+ ```
42
+
43
+ Для проєктів **без** нативних аддонів standalone-бінарник на alpine лишається каноном (див. `compile.mdc`).
@@ -0,0 +1,7 @@
1
+ ## Мінімальний тег для nginx-unprivileged
2
+
3
+ Якщо використовується nginx, то **використовуй** `mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim` (і не `latest` / `alpine`).
4
+
5
+ Тег `alpine-slim` є обов'язковим для всіх `FROM` з образом `nginxinc/nginx-unprivileged` — будь-який інший тег (включаючи без тега) прапорцюється як порушення.
6
+
7
+ Перевіряє `getNginxAlpineSlimTagHint` у **`npm/rules/docker/js/lint.mjs`**.
@@ -0,0 +1,37 @@
1
+ ## nginx-unprivileged — без USER, із --chown
2
+
3
+ Окрема гілка для фронтенду на базі **`nginxinc/nginx-unprivileged`** (будь-який тег, з/без `mirror.gcr.io/`-префікса). Цей образ **уже** оголошує `USER 101` і `EXPOSE 8080`, тож у фінальному stage **не потрібні** жодні явні `USER`-інструкції:
4
+
5
+ - **жодного `USER root` / `USER 0`** для білд-кроків: він перезатирає успадкований `USER 101`, і якщо потім не повернути non-root — фінальний образ лишається root, а k8s із `runAsNonRoot: true` падає з `CreateContainerConfigError`;
6
+ - **жодного switch-back** `USER 101` / `USER nginx` наприкінці stage — це лише симптом зайвого `USER root` на початку (повертати треба саме **числовим** UID, бо kubelet не підтверджує non-root за іменем `nginx`). Канон — взагалі не виходити з-під дефолтного 101;
7
+ - **`COPY`/`ADD` лише з `--chown`** (канон — `--chown=nginx:nginx`): без нього файли копіюються власником root і дефолтний non-root користувач (uid=101) не зможе читати статику.
8
+
9
+ Build-stage не чіпаємо — там root і tooling норма. Перевіряє **`npm/rules/docker/lib/docker-nginx-user.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
10
+
11
+ ### Антипатерн (це правило ловить)
12
+
13
+ ```dockerfile
14
+ FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
15
+ USER root
16
+ COPY ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
17
+ COPY --from=build /app/dist ./
18
+ RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
19
+ USER 101 # повернення назад — симптом того, що був зайвий USER root
20
+ EXPOSE 8080
21
+ ```
22
+
23
+ ### Канон (привести до цього)
24
+
25
+ ```dockerfile
26
+ FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
27
+
28
+ COPY --chown=nginx:nginx ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
29
+
30
+ WORKDIR /usr/share/nginx/html
31
+
32
+ COPY --from=build --chown=nginx:nginx /app/dist ./
33
+
34
+ RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
35
+ ```
36
+
37
+ (без жодного `USER`, gzip під дефолтним користувачем 101; `EXPOSE 8080` теж зайвий — база вже його оголошує)
@@ -0,0 +1,39 @@
1
+ ## Non-root принцип у фінальному stage
2
+
3
+ Для всіх образів потрібно щоб використовувся non-root принцип. **Спосіб** досягнення non-root залежить від **бази**, а не від зміни ОС — змінювати дистрибутив (Alpine→Debian) заради лише non-root **не треба**. Два шляхи:
4
+
5
+ - **standalone-бінарник на `alpine:latest`** (секція компіляції) — у `alpine` немає готового non-root користувача, тож його створюємо явно: `addgroup -g 1000 app && adduser -D -u 1000 -G app app` + `COPY --chown=app:app` + `USER app` (приклад нижче);
6
+ - **ship `node_modules` на `mirror.gcr.io/oven/bun:alpine`** (виняток: нативний `.node`-аддон) — користувач `bun` (uid/gid 1000) **уже в базі**, тож достатньо `COPY --chown=bun:bun …` + `USER bun`; базу на Debian-slim міняти **не треба** — це той самий антипатерн, що й у переліку фінальних образів.
7
+
8
+ ### Приклад для standalone-бінарника на alpine
9
+
10
+ ```dockerfile
11
+ # Stage 1
12
+ FROM oven/bun:alpine AS build-env
13
+ WORKDIR /app
14
+ COPY package.json bunfig.toml .
15
+ RUN bun install --production
16
+ COPY ./src ./src
17
+ RUN bun build --compile --outfile app ./src/index.js
18
+
19
+ # Stage 2
20
+ FROM alpine:latest
21
+ RUN apk add --no-cache libstdc++ libgcc tzdata
22
+
23
+ # Додати non-root user
24
+ RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
25
+
26
+ WORKDIR /app
27
+
28
+ # Змінити власника файлу
29
+ COPY --from=build-env --chown=app:app /app/app ./app
30
+
31
+ # Запускати як non-root
32
+ USER app
33
+
34
+ CMD ["./app"]
35
+ ```
36
+
37
+ Перевіряє `getNonRootRuntimeHint` у **`npm/rules/docker/js/lint.mjs`**.
38
+
39
+ > **Примітка:** для `nginxinc/nginx-unprivileged` канон відрізняється — дивись `nginx-user.mdc`.
@@ -7,211 +7,28 @@ alwaysApply: false
7
7
 
8
8
  # Docker — hadolint
9
9
 
10
- Для образів з Docker Hub **`oven/bun`**, **`alpine`**, **`nginxinc/nginx-unprivileged`**, **`node`** у **`FROM`** треба вказувати дзеркало GCR, а не pull напряму з Hub: **`mirror.gcr.io/oven/bun`**, **`mirror.gcr.io/library/alpine`**, **`mirror.gcr.io/nginxinc/nginx-unprivileged`**, **`mirror.gcr.io/library/node`**. Перевіряє **`rules/docker/fix.mjs`**, деталі в **`npm/rules/docker/js/lint/docker-mirror.mjs`**.
10
+ Правило перевіряє Dockerfile / Containerfile: GCR-дзеркало замість Docker Hub, multistage build з дозволеними runtime-образами, компіляцію bun-проєктів у бінарник, виняток для нативних `.node`-аддонів, non-root у фінальному stage, nginx-специфічні вимоги та hadolint CI-workflow.
11
11
 
12
- Також Dockerfile/Containerfile **має бути multistage build**: окремий build stage (залежності/компіляція) і окремий runtime stage. У фінальному stage дозволені лише мінімальні базові образи:
12
+ ## Активація
13
13
 
14
- - **backend (типово)**: `mirror.gcr.io/library/alpine:*`
15
- - **ультра-легкі (glibc / одна статична збірка)**: `scratch` — тільки як `FROM scratch` (офіційний порожній базовий шар), коли весь **runtime** уже в `COPY --from=…`
16
- - **glibc, Debian (slim)**: `mirror.gcr.io/library/debian:*` **лише** з тегом, у якому є `slim` (наприклад `bookworm-slim`, `trixie-slim`), а не `bookworm` без `slim`. Debian-slim виправданий **лише** коли потрібен саме glibc-рантайм (нативні glibc-залежності, prebuilds без musl-варіанта). **Non-root сам по собі не є підставою** переходити сюди: `mirror.gcr.io/oven/bun:alpine` уже має користувача `bun` (uid/gid 1000), тож `USER bun` дає non-root **без зміни бази**. Перехід Alpine→Debian заради лише non-root — **антипатерн** (більший образ + зайва musl→glibc міграція bun-бінарника)
17
- - **виняток (інтерпретовані стеки)**: `mirror.gcr.io/library/php:*` або `mirror.gcr.io/library/python:*` — якщо сервіс має крутитися в офіційному runtime PHP чи Python, а не як один бінарник на Alpine; інакше лишай **alpine** у фінальному stage
18
- - **frontend**: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` або `mirror.gcr.io/openresty/openresty:*`
14
+ Спрацьовує на всі файли `Dockerfile*`; `check docker` (`fix.mjs`) обробляє також `Containerfile` та `Containerfile.*`.
19
15
 
20
- Це стримує зайвий build tooling (Bun, **node_modules** зі збірки) у фінальному образі; для **alpine** / **nginx** / **openresty** у **runtime** лишаються лише відповідні вимоги, для **php** / **python** (виняток) — цільовий інтерпретований **stack**; **scratch** і **debian** з тегом **`*slim*`** — коли glibc і мінімальне оточення Debian важливіші за musl в **alpine**.
16
+ ## Швидкий gate через conftest
21
17
 
22
- ## Мінімальні образи
18
+ [docker-lint_docker_yml](./policy/lint_docker_yml/lint_docker_yml.mdc)
23
19
 
24
- Якщо використовується nginx, то **використовуй** `mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim` (і не `latest` / `alpine`).
20
+ [docker-mirror](./js/mirror.mdc)
25
21
 
26
- ## компіляція
22
+ [docker-multistage](./js/multistage.mdc)
27
23
 
28
- Якщо проект має bun install крок, та не є фронтенд проектом (тобто не має bun build крок), **і не має нативного `.node`-аддона з динамічним завантаженням** (див. наступну секцію), то потрібно щоб була компіляція коду, і далі у фінальному образі був тільки бінарник і Цей образ не містив компілятора, npm, Bun — тільки runtime libs. Наприклад:
24
+ [docker-compile](./js/compile.mdc)
29
25
 
30
- ```dockerfile
31
- FROM mirror.gcr.io/oven/bun:alpine AS build-env
26
+ [docker-native-addon](./js/native-addon.mdc)
32
27
 
33
- WORKDIR /app
28
+ [docker-non-root](./js/non-root.mdc)
34
29
 
35
- ENV NODE_ENV=production
30
+ [docker-nginx-tag](./js/nginx-tag.mdc)
36
31
 
37
- COPY package.json .
38
- COPY bunfig.toml .
32
+ [docker-nginx-user](./js/nginx-user.mdc)
39
33
 
40
- RUN bun install --production
41
-
42
- COPY ./src ./src
43
-
44
- # Компілюємо в бінарник
45
- RUN bun build --compile --outfile app ./src/index.js
46
-
47
- FROM mirror.gcr.io/library/alpine:latest
48
-
49
- # (libstdc++ libgcc) для Bun runtime, (tzdata) для часового поясу
50
- RUN apk add --no-cache libstdc++ libgcc tzdata
51
-
52
- WORKDIR /app
53
-
54
- COPY --from=build-env /app/app ./app
55
-
56
- CMD ["./app"]
57
- ```
58
-
59
- ### виняток: нативний `.node`-аддон (sharp / @img/* / argon2)
60
-
61
- Якщо в `package.json#dependencies` є нативний `.node`-аддон, який вантажиться через **динамічний `require`** — передусім **`sharp`**; той самий клас — **`@img/*`**, **`argon2`** — його **не можна** пакувати через `bun build --compile`.
62
-
63
- `bun build --compile` не трейсить `require(\`@img/sharp-${platform}/sharp.node\`)` і не вшиває нативний біндинг → компільований бінарник падає в рантаймі: `Could not load the "sharp" module using the linuxmusl-arm64 runtime`. Доведено реальними docker-збірками (bun 1.3.14, sharp 0.34.5); відтворюється і на darwin-arm64 (тобто не musl/glibc-залежне). `apk add vips` **НЕ лікує** — він дає системний libvips, а бракує саме `sharp.node`.
64
-
65
- Канон — ship `node_modules` і запускати через `bun <entry>` на базі `mirror.gcr.io/oven/bun:alpine` (це легітимний виняток до правила «лише alpine/scratch у фінальному stage» — тут потрібен саме bun-рантайм). База `oven/bun` уже має non-root користувача `bun` (uid/gid 1000). Entry бери з наявного `--outfile`-таргета / `package.json#main` / `scripts.start`; якщо не визначити — лиши TODO-маркер, не вгадуй.
66
-
67
- Перевіряє **`npm/rules/docker/lib/docker-native-addon.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**). Список нативних аддонів — розширювана константа `NATIVE_ADDON_PACKAGES` / `NATIVE_ADDON_SCOPES` у чек-модулі.
68
-
69
- #### Антипатерн (це правило ловить)
70
-
71
- ```dockerfile
72
- RUN bun build --compile --outfile app ./src/index.js # ← з sharp у deps
73
- FROM mirror.gcr.io/library/alpine:latest
74
- RUN apk add --no-cache ... vips # системний vips не рятує
75
- COPY --from=build-env --chown=app:app /app/app ./app
76
- USER app
77
- CMD ["./app"]
78
- ```
79
-
80
- #### Канон (привести до цього)
81
-
82
- ```dockerfile
83
- FROM mirror.gcr.io/oven/bun:alpine AS build-env
84
- WORKDIR /app
85
- ENV NODE_ENV=production
86
- COPY package.json .
87
- RUN bun install --production
88
- COPY ./src ./src
89
-
90
- FROM mirror.gcr.io/oven/bun:alpine
91
- RUN apk add --no-cache tzdata
92
- WORKDIR /app
93
- # база oven/bun має non-root користувача bun (uid/gid 1000)
94
- COPY --from=build-env --chown=bun:bun /app/node_modules ./node_modules
95
- COPY --from=build-env --chown=bun:bun /app/src ./src
96
- COPY --from=build-env --chown=bun:bun /app/package.json ./package.json
97
- USER bun
98
- CMD ["bun", "src/index.js"]
99
- ```
100
-
101
- Для проєктів **без** нативних аддонів standalone-бінарник на alpine лишається каноном (секція «компіляція» вище).
102
-
103
- ## не превілейований образ
104
-
105
- Для всіх образів потрібно щоб використовувся non-root принцип. **Спосіб** досягнення non-root залежить від **бази**, а не від зміни ОС — змінювати дистрибутив (Alpine→Debian) заради лише non-root **не треба**. Два шляхи:
106
-
107
- - **standalone-бінарник на `alpine:latest`** (секція «компіляція») — у `alpine` немає готового non-root користувача, тож його створюємо явно: `addgroup -g 1000 app && adduser -D -u 1000 -G app app` + `COPY --chown=app:app` + `USER app` (приклад нижче);
108
- - **ship `node_modules` на `mirror.gcr.io/oven/bun:alpine`** (секція «виняток: нативний `.node`-аддон») — користувач `bun` (uid/gid 1000) **уже в базі**, тож достатньо `COPY --chown=bun:bun …` + `USER bun`; базу на Debian-slim міняти **не треба** — це той самий антипатерн, що й у переліку фінальних образів вище.
109
-
110
- Приклад для standalone-бінарника на alpine:
111
-
112
- ```dockerfile
113
- # Stage 1
114
- FROM oven/bun:alpine AS build-env
115
- WORKDIR /app
116
- COPY package.json bunfig.toml .
117
- RUN bun install --production
118
- COPY ./src ./src
119
- RUN bun build --compile --outfile app ./src/index.js
120
-
121
- # Stage 2
122
- FROM alpine:latest
123
- RUN apk add --no-cache libstdc++ libgcc tzdata
124
-
125
- # ✅ Додати non-root user
126
- RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
127
-
128
- WORKDIR /app
129
-
130
- # ✅ Змінити власника файлу
131
- COPY --from=build-env --chown=app:app /app/app ./app
132
-
133
- # ✅ Запускати як non-root
134
- USER app
135
-
136
- CMD ["./app"]
137
-
138
- ```
139
-
140
- ### nginx-unprivileged — без USER, із --chown
141
-
142
- Окрема гілка для фронтенду на базі **`nginxinc/nginx-unprivileged`** (будь-який тег, з/без `mirror.gcr.io/`-префікса). Цей образ **уже** оголошує `USER 101` і `EXPOSE 8080`, тож у фінальному stage **не потрібні** жодні явні `USER`-інструкції:
143
-
144
- - **жодного `USER root` / `USER 0`** для білд-кроків: він перезатирає успадкований `USER 101`, і якщо потім не повернути non-root — фінальний образ лишається root, а k8s із `runAsNonRoot: true` падає з `CreateContainerConfigError`;
145
- - **жодного switch-back** `USER 101` / `USER nginx` наприкінці stage — це лише симптом зайвого `USER root` на початку (повертати треба саме **числовим** UID, бо kubelet не підтверджує non-root за іменем `nginx`). Канон — взагалі не виходити з-під дефолтного 101;
146
- - **`COPY`/`ADD` лише з `--chown`** (канон — `--chown=nginx:nginx`): без нього файли копіюються власником root і дефолтний non-root користувач (uid=101) не зможе читати статику.
147
-
148
- Build-stage не чіпаємо — там root і tooling норма. Перевіряє **`npm/rules/docker/lib/docker-nginx-user.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
149
-
150
- #### Антипатерн (це правило ловить)
151
-
152
- ```dockerfile
153
- FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
154
- USER root
155
- COPY ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
156
- COPY --from=build /app/dist ./
157
- RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
158
- USER 101 # повернення назад — симптом того, що був зайвий USER root
159
- EXPOSE 8080
160
- ```
161
-
162
- #### Канон (привести до цього)
163
-
164
- ```dockerfile
165
- FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
166
-
167
- COPY --chown=nginx:nginx ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
168
-
169
- WORKDIR /usr/share/nginx/html
170
-
171
- COPY --from=build --chown=nginx:nginx /app/dist ./
172
-
173
- RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
174
- ```
175
-
176
- (без жодного `USER`, gzip під дефолтним користувачем 101; `EXPOSE 8080` теж зайвий — база вже його оголошує)
177
-
178
- ## Область
179
-
180
- - Усі файли з іменем **`Dockerfile`** або **`Dockerfile.*`** (наприклад `Dockerfile.prod`) у репозиторії, крім ігнорованих каталогів (`node_modules`, `.git`, `dist`, …) — як у **`rules/docker/fix.mjs`**.
181
- - Також скрипт перевірки обробляє **`Containerfile`** та **`Containerfile.*`** (Podman / альтернативні імена), навіть якщо glob правила спрацьовує переважно на `Dockerfile*`.
182
-
183
- ## lint-docker
184
-
185
- CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить правило **`n-cursor lint docker`** (реалізація — **`npm/rules/docker/js/lint.mjs`**).
186
-
187
- **Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`rules/docker/fix.mjs`).
188
-
189
- Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`rules/docker/fix.mjs`**. Виклик **`hadolint`** як **нативного бінарника** через **`ensureTool`** (PATH → кеш → авто-install brew/scoop/GitHub Release; **без** `docker run`) — спільна логіка **`npm/rules/docker/lib/docker-hadolint.mjs`**.
190
-
191
- Окремий `package.json`-скрипт `lint-docker` не потрібен і не перевіряється — єдина точка входу для правила: **`n-cursor lint docker`**.
192
-
193
- Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
194
-
195
- - Канон: [lint-docker.yml.snippet.yml](./policy/lint_docker_yml/template/lint-docker.yml.snippet.yml)
196
-
197
- Локально hadolint авто-встановлюється через **`ensureTool`** (latest, без піну версії). У CI встанови його кроком із workflow-сніпета (curl-download бінарника — без `docker run`).
198
-
199
- ## Запуск
200
-
201
- 1. **`n-cursor lint docker`** — **`js/lint.mjs`**: **`Dockerfile`** та **`*.Dockerfile`** (див. **`lint-docker`**); у CI використовуй **`n-cursor lint docker --read-only`** і встанови hadolint (приклад у workflow).
202
- 2. **`npx @nitra/cursor fix docker`** — **`rules/docker/fix.mjs`**, виклик hadolint як у **`docker-hadolint.mjs`** (нативний бінарник через **`ensureTool`**; **без** `docker run`).
203
- 3. Кореневий **`.hadolint.yaml`**: вимкнення правил, trusted registries — [документація](https://github.com/hadolint/hadolint#configure). Щоб не додавати **`# hadolint ignore=DL3007`** у кожному **`FROM`** з **`:latest`**, у корені репозиторію задати глобально:
204
-
205
- ```yaml title=".hadolint.yaml"
206
- ignored:
207
- - DL3007
208
- - DL3008
209
- - DL3018
210
- ```
211
-
212
- Де DL3007 - «Не використовуй тег latest у FROM»
213
- Де DL3018 - «Піни версії пакетів у apk add»
214
-
215
- Якщо немає файлів у межах відповідного набору (**`lint-docker`** або **`check docker`**) — перевірка пропускається (exit 0).
216
-
217
- Винятки: **`# hadolint ignore=DL3008`** (або інший код) у Dockerfile або **`ignored`** у **`.hadolint.yaml`** (наприклад **DL3007** для **`:latest`** — див. вище).
34
+ [docker-hadolint](./js/hadolint.mdc)
@@ -0,0 +1,14 @@
1
+ ## Перевірка `.github/workflows/lint-docker.yml`
2
+
3
+ Rego-пакет: `docker.lint_docker_yml`
4
+
5
+ Цільовий файл: `.github/workflows/lint-docker.yml`
6
+
7
+ Перевіряє, що CI-workflow lint-docker відповідає канонічному сніпету:
8
+
9
+ - `on.push.paths` містить усі три glob-маски: `**/Dockerfile`, `**/*.Dockerfile`, `**/*.dockerfile`
10
+ - присутні кроки `uses: actions/checkout@v6` та `uses: ./.github/actions/setup-bun-deps`
11
+ - крок install hadolint містить рядок із версією `v2.12.0` (substring-перевірка)
12
+ - крок lint містить `n-cursor lint docker --read-only`
13
+
14
+ Канонічний шаблон: [lint-docker.yml.snippet.yml](./template/lint-docker.yml.snippet.yml)
@@ -20,4 +20,4 @@ bun add -d @nitra/efes-shared
20
20
 
21
21
  Пакети (директорія в **`npm/rules/efes/policy/`** → namespace → що перевіряє):
22
22
 
23
- - **`package_json_shared/`** → `efes.package_json_shared` — у кореневому `package.json` `devDependencies` має містити `@nitra/efes-shared` (presence-only, версію не фіксуємо). **Цільові файли:** `package.json`.
23
+ [efes-package_json_shared](./policy/package_json_shared/package_json_shared.mdc)
@@ -0,0 +1,30 @@
1
+ ## Наявність `@nitra/efes-shared` у `devDependencies`
2
+
3
+ Rego-пакет: `efes.package_json_shared`
4
+
5
+ **Цільовий файл:** `package.json` (корінь проєкту, обовʼязковий).
6
+
7
+ **Що перевіряється:** у `devDependencies` кореневого `package.json` має бути ключ `@nitra/efes-shared`. Версію не фіксуємо — лише presence. Наявність лише у `dependencies` (не `devDependencies`) — теж помилка.
8
+
9
+ **Виправлення:**
10
+
11
+ ```bash
12
+ bun add -d @nitra/efes-shared
13
+ ```
14
+
15
+ **Приклади:**
16
+
17
+ ✓ правильно — `devDependencies` містить пакет:
18
+ ```json
19
+ { "devDependencies": { "@nitra/efes-shared": "^1.0.0" } }
20
+ ```
21
+
22
+ ✗ неправильно — `devDependencies` відсутній або порожній:
23
+ ```json
24
+ { "name": "my-project" }
25
+ ```
26
+
27
+ ✗ неправильно — пакет лише у `dependencies`:
28
+ ```json
29
+ { "dependencies": { "@nitra/efes-shared": "^1.0.0" } }
30
+ ```
@@ -0,0 +1,15 @@
1
+ ## Лінт-тулчейн: actionlint, zizmor, n-cursor lint ga
2
+
3
+ **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Запуск — через **`n-cursor lint ga`** (CI — `--read-only`; бінарка з `node_modules/.bin/` пакету `@nitra/cursor`), який робить preflight на `shellcheck` і послідовно запускає `actionlint` та `zizmor`. Окремого `package.json`-скрипта немає.
4
+
5
+ > Виклик через bin-ім'я `n-cursor` (а **не** `npx @nitra/cursor`): `bun x`/`npx` для скоупованого пакету з одним bin-ім'ям повертає 0 без виконання, тому в CI-кроці `run:` використовуй саме `n-cursor lint <rule>`.
6
+
7
+ CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, потім запускає `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
8
+
9
+ Послідовність кроків `n-cursor lint ga`:
10
+
11
+ 1. `ensureTool`: `shellcheck` і `conftest` (авто-install або hard-fail)
12
+ 2. preflight: `uv` (для `uvx zizmor`) — hint-only, без авто-install
13
+ 3. `bunx github-actionlint`
14
+ 4. `uvx zizmor --offline --collect=workflows .`
15
+ 5. `rules/ga/check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file перевірки правил `ga.mdc`
@@ -0,0 +1,35 @@
1
+ ## Канонічні обов'язкові workflow-файли
2
+
3
+ Кожен з цих файлів перевіряється окремим Rego-пакетом через conftest з відповідним `--namespace`.
4
+
5
+ ### `clean-ga-workflows.yml`
6
+
7
+ Очищення старих GitHub Actions workflow runs за розкладом.
8
+
9
+ - Канон: [clean-ga-workflows.yml.snippet.yml](./policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml)
10
+
11
+ Rego-пакет `ga.clean_ga_workflows` перевіряє: `name`, `on.schedule[].cron`, `workflow_dispatch: {}`, `jobs.cleanup_old_workflows`, `runs-on`, `permissions` (actions: write, contents: read), перший крок (`uses`, `with.token`, `with.save_period`, `with.save_min_runs_number`).
12
+
13
+ ### `clean-merged-branch.yml`
14
+
15
+ Видалення злитих гілок за розкладом та вручну.
16
+
17
+ - Канон: [clean-merged-branch.yml.snippet.yml](./policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml)
18
+
19
+ Інші гілки в `ignore_branches` — допустимо. Rego-пакет `ga.clean_merged_branch` перевіряє: `name`, `on.schedule[].cron`, `workflow_dispatch: {}`, `jobs.cleanup_old_branches`, `permissions`, крок 0 (`id`, `uses`, `with.github_token`, `with.last_commit_age_days`, `with.ignore_branches`, `with.dry_run: no`), крок 1 (name, env.DELETED_BRANCHES, run з echo Deleted branches).
20
+
21
+ ### `lint-ga.yml`
22
+
23
+ CI-лінт GitHub Actions workflows через actionlint + zizmor.
24
+
25
+ - Канон: [lint-ga.yml.snippet.yml](./policy/lint_ga/template/lint-ga.yml.snippet.yml)
26
+
27
+ Rego-пакет `ga.lint_ga` перевіряє: `name`, `on.push.branches` і `on.pull_request.branches` (мають містити dev і main), `on.push.paths` (мають містити `.github/actions/**` і `.github/workflows/**`), `jobs.lint-ga`, `runs-on`, `permissions.contents`, наявність кроку Install conftest, крок `run: n-cursor lint ga --read-only`.
28
+
29
+ ### `git-ai.yml`
30
+
31
+ Автоматизація PR-коментарів через git-ai CI.
32
+
33
+ - Канон: [git-ai.yml.snippet.yml](./policy/git_ai/template/git-ai.yml.snippet.yml)
34
+
35
+ Rego-пакет `ga.git_ai` перевіряє: `name`, `on.pull_request.types` (має містити `closed`), `jobs.git-ai`, умову `if:` job (merged PR), `permissions.contents`, наявність кроку встановлення (`https://usegitai.com/install.sh`) та запуску (`git-ai ci github run`).
@@ -0,0 +1,17 @@
1
+ ## VS Code — розширення та налаштування для GitHub Actions
2
+
3
+ ### `.vscode/extensions.json`
4
+
5
+ Має рекомендувати `github.vscode-github-actions`:
6
+
7
+ - Канон: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
8
+
9
+ Rego-пакет `ga.vscode_extensions`: кожне розширення з template має бути в `recommendations`. Додаткові рекомендації від інших правил — допустимі.
10
+
11
+ ### `.vscode/settings.json`
12
+
13
+ Для мови `github-actions-workflow` має `editor.defaultFormatter = "oxc.oxc-vscode"`:
14
+
15
+ - Канон: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
16
+
17
+ Rego-пакет `ga.vscode_settings`: перевіряє всі ключі з template у відповідному language-block.