@nitra/cursor 12.8.6 → 12.8.7

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 (187) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +1 -1
  3. package/rules/adr/js/hooks.mdc +32 -0
  4. package/rules/adr/js/madr_format.mdc +96 -0
  5. package/rules/adr/js/settings_policy.mdc +34 -0
  6. package/rules/adr/main.mdc +13 -95
  7. package/rules/bun/js/bunfig.mdc +12 -0
  8. package/rules/bun/js/layout.mdc +60 -0
  9. package/rules/bun/js/lint.mdc +9 -0
  10. package/rules/bun/js/package_json.mdc +19 -0
  11. package/rules/bun/main.mdc +9 -61
  12. package/rules/capacitor/js/ios_spm.mdc +69 -0
  13. package/rules/capacitor/js/version.mdc +29 -0
  14. package/rules/capacitor/main.mdc +8 -22
  15. package/rules/changelog/js/agent-workflow.mdc +15 -0
  16. package/rules/changelog/js/changelog-format.mdc +33 -0
  17. package/rules/changelog/js/comparison-models.mdc +40 -0
  18. package/rules/changelog/main.mdc +4 -98
  19. package/rules/ci4/js/marksman_config.mdc +31 -0
  20. package/rules/ci4/js/vscode_extensions.mdc +33 -0
  21. package/rules/ci4/main.mdc +14 -14
  22. package/rules/docker/js/compile.mdc +44 -0
  23. package/rules/docker/js/hadolint.mdc +50 -0
  24. package/rules/docker/js/mirror.mdc +13 -0
  25. package/rules/docker/js/multistage.mdc +13 -0
  26. package/rules/docker/js/native-addon.mdc +43 -0
  27. package/rules/docker/js/nginx-tag.mdc +7 -0
  28. package/rules/docker/js/nginx-user.mdc +37 -0
  29. package/rules/docker/js/non-root.mdc +39 -0
  30. package/rules/docker/main.mdc +15 -196
  31. package/rules/ga/js/lint_toolchain.mdc +15 -0
  32. package/rules/ga/js/required_workflows.mdc +35 -0
  33. package/rules/ga/js/vscode.mdc +17 -0
  34. package/rules/ga/js/workflow_common.mdc +108 -0
  35. package/rules/ga/js/workflows.mdc +32 -0
  36. package/rules/ga/js/zizmor.mdc +7 -0
  37. package/rules/ga/main.mdc +17 -125
  38. package/rules/graphql/js/tooling.mdc +13 -0
  39. package/rules/graphql/js/vscode_extensions.mdc +13 -0
  40. package/rules/graphql/main.mdc +3 -22
  41. package/rules/hasura/js/internal_urls.mdc +27 -0
  42. package/rules/hasura/js/migrations.mdc +13 -0
  43. package/rules/hasura/js/svc_hl.mdc +17 -0
  44. package/rules/hasura/main.mdc +8 -30
  45. package/rules/image-avif/js/avif_generation.mdc +26 -0
  46. package/rules/image-avif/js/package_json_optout.mdc +21 -0
  47. package/rules/image-avif/main.mdc +7 -34
  48. package/rules/image-compress/js/package_json.mdc +7 -0
  49. package/rules/image-compress/js/package_setup.mdc +13 -0
  50. package/rules/image-compress/main.mdc +4 -12
  51. package/rules/js/docs/index.md +3 -3
  52. package/rules/js/js/dep-policy.mdc +17 -0
  53. package/rules/js/js/eslint-config.mdc +28 -0
  54. package/rules/js/js/extensions.mdc +8 -0
  55. package/rules/js/js/file-extensions.mdc +12 -0
  56. package/rules/js/js/for-in.mdc +26 -0
  57. package/rules/js/js/jscpd.mdc +42 -0
  58. package/rules/js/js/knip.mdc +15 -0
  59. package/rules/js/js/lint-js-workflow.mdc +58 -0
  60. package/rules/js/js/oxlintrc.mdc +20 -0
  61. package/rules/js/js/package-json.mdc +31 -0
  62. package/rules/js/js/tests.mdc +9 -0
  63. package/rules/js/js/utils-lib-structure.mdc +15 -0
  64. package/rules/js/main.mdc +21 -214
  65. package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
  66. package/rules/js-bun-db/js/connection.mdc +42 -0
  67. package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
  68. package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
  69. package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
  70. package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
  71. package/rules/js-bun-db/js/query-safety.mdc +117 -0
  72. package/rules/js-bun-db/js/sql-array.mdc +88 -0
  73. package/rules/js-bun-db/js/unsafe.mdc +65 -0
  74. package/rules/js-bun-db/main.mdc +15 -605
  75. package/rules/js-bun-redis/js/imports.mdc +47 -0
  76. package/rules/js-bun-redis/js/package_json.mdc +44 -0
  77. package/rules/js-bun-redis/main.mdc +3 -11
  78. package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
  79. package/rules/js-mssql/js/mssql-pool.mdc +56 -0
  80. package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
  81. package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
  82. package/rules/js-mssql/js/mssql-version.mdc +7 -0
  83. package/rules/js-mssql/main.mdc +10 -198
  84. package/rules/js-run/js/check-env.mdc +35 -0
  85. package/rules/js-run/js/conn-aliases.mdc +109 -0
  86. package/rules/js-run/js/jsconfig.mdc +20 -0
  87. package/rules/js-run/js/otel-configmap.mdc +6 -0
  88. package/rules/js-run/js/pino.mdc +6 -0
  89. package/rules/js-run/js/project-structure.mdc +11 -0
  90. package/rules/js-run/js/runtime.mdc +14 -0
  91. package/rules/js-run/js/scope.mdc +11 -0
  92. package/rules/js-run/js/settimeout.mdc +11 -0
  93. package/rules/js-run/js/temporal.mdc +5 -0
  94. package/rules/js-run/main.mdc +16 -218
  95. package/rules/k8s/js/configmap.mdc +41 -0
  96. package/rules/k8s/js/deployment_resources.mdc +49 -0
  97. package/rules/k8s/js/hasura_httproute.mdc +91 -0
  98. package/rules/k8s/js/hpa_apiversion.mdc +27 -0
  99. package/rules/k8s/js/ingress_gateway.mdc +16 -0
  100. package/rules/k8s/js/kustomize_structure.mdc +144 -0
  101. package/rules/k8s/js/lint_k8s.mdc +72 -0
  102. package/rules/k8s/js/multidoc_yaml.mdc +5 -0
  103. package/rules/k8s/js/network_policy.mdc +136 -0
  104. package/rules/k8s/js/schema_modeline.mdc +57 -0
  105. package/rules/k8s/js/service.mdc +44 -0
  106. package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
  107. package/rules/k8s/main.mdc +30 -843
  108. package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
  109. package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
  110. package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
  111. package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
  112. package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
  113. package/rules/nginx-default-tpl/main.mdc +6 -112
  114. package/rules/npm-module/js/docs/index.md +5 -5
  115. package/rules/npm-module/js/docs/rule_meta.md +6 -6
  116. package/rules/npm-module/js/docs/skill_meta.md +8 -8
  117. package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
  118. package/rules/npm-module/js/package_structure.mdc +62 -0
  119. package/rules/npm-module/js/rule_meta.mdc +11 -0
  120. package/rules/npm-module/js/skill_meta.mdc +11 -0
  121. package/rules/npm-module/main.mdc +10 -55
  122. package/rules/php/js/lint_php_yml.mdc +12 -0
  123. package/rules/php/js/tooling.mdc +66 -0
  124. package/rules/php/main.mdc +7 -66
  125. package/rules/python/js/lint_python_yml.mdc +23 -0
  126. package/rules/python/js/pyproject_toml.mdc +32 -0
  127. package/rules/python/js/tooling.mdc +23 -0
  128. package/rules/python/main.mdc +9 -33
  129. package/rules/rego/js/rego-lint.mdc +31 -0
  130. package/rules/rego/js/vscode_extensions.mdc +11 -0
  131. package/rules/rego/js/vscode_settings.mdc +13 -0
  132. package/rules/rego/main.mdc +8 -24
  133. package/rules/rust/js/coverage.mdc +28 -0
  134. package/rules/rust/js/lint.mdc +22 -0
  135. package/rules/rust/js/tauri_composition.mdc +8 -0
  136. package/rules/rust/js/vscode_extensions.mdc +12 -0
  137. package/rules/rust/main.mdc +8 -38
  138. package/rules/security/js/rego_policies.mdc +15 -0
  139. package/rules/security/js/sample_secret.mdc +19 -0
  140. package/rules/security/js/trufflehog.mdc +21 -0
  141. package/rules/security/main.mdc +7 -35
  142. package/rules/style/js/admin-table.mdc +88 -0
  143. package/rules/style/js/colors.mdc +21 -0
  144. package/rules/style/js/gap.mdc +22 -0
  145. package/rules/style/js/quasar-fixes.mdc +32 -0
  146. package/rules/style/js/quasar.mdc +7 -0
  147. package/rules/style/js/tooling.mdc +85 -0
  148. package/rules/style/main.mdc +13 -253
  149. package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
  150. package/rules/tauri/js/tool_surface.mdc +21 -0
  151. package/rules/tauri/js/tooling.mdc +25 -0
  152. package/rules/tauri/main.mdc +8 -78
  153. package/rules/test/js/cargo_mutants_config.mdc +18 -0
  154. package/rules/test/js/docs/index.md +7 -7
  155. package/rules/test/js/location.mdc +52 -0
  156. package/rules/test/js/no-console-store-restore.mdc +11 -0
  157. package/rules/test/js/no-process-chdir.mdc +15 -0
  158. package/rules/test/js/no-relative-fs-path.mdc +22 -0
  159. package/rules/test/js/sandbox-aware-test.mdc +28 -0
  160. package/rules/test/js/stryker_config.mdc +26 -0
  161. package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
  162. package/rules/test/main.mdc +18 -184
  163. package/rules/text/js/ci-lint-text.mdc +15 -0
  164. package/rules/text/js/cspell.mdc +81 -0
  165. package/rules/text/js/dotenv-linter.mdc +16 -0
  166. package/rules/text/js/forbidden-prettier.mdc +13 -0
  167. package/rules/text/js/markdownlint.mdc +25 -0
  168. package/rules/text/js/oxfmt.mdc +35 -0
  169. package/rules/text/js/package-json.mdc +26 -0
  170. package/rules/text/js/shellcheck.mdc +18 -0
  171. package/rules/text/js/v8r.mdc +23 -0
  172. package/rules/text/js/vscode.mdc +86 -0
  173. package/rules/text/main.mdc +20 -237
  174. package/rules/vue/js/composition-api.mdc +82 -0
  175. package/rules/vue/js/nheader-layout.mdc +171 -0
  176. package/rules/vue/js/node-imports.mdc +25 -0
  177. package/rules/vue/js/quasar-ui.mdc +32 -0
  178. package/rules/vue/js/structure.mdc +101 -0
  179. package/rules/vue/js/testing.mdc +32 -0
  180. package/rules/vue/js/tfm-translations.mdc +26 -0
  181. package/rules/vue/js/vite-config.mdc +126 -0
  182. package/rules/vue/js/vite-env.mdc +55 -0
  183. package/rules/vue/js/vue-imports.mdc +25 -0
  184. package/rules/vue/main.mdc +16 -640
  185. package/scripts/docs/index.md +16 -16
  186. package/scripts/lib/docs/index.md +36 -36
  187. package/scripts/utils/docs/index.md +14 -14
@@ -5,30 +5,16 @@ alwaysApply: false
5
5
  version: '1.1'
6
6
  ---
7
7
 
8
- ## Версія Capacitor
8
+ Правило перевіряє версію `@capacitor/core` у `package.json` та коректність використання CocoaPods/SPM у iOS-шарі Capacitor-проєкту.
9
9
 
10
- У `package.json` (у **корені** репозиторію чи **workspace**-пакеті) оголошення **`@capacitor/core`**
11
- має вказувати діапазон, **сумісний лише з мажорною версією 8 і вище** (наприклад `^8.0.0`).
12
- **`*`**, `latest` і діапазони, де можлива 7-мажор, — неприйнятні.
10
+ [capacitor-version](./js/version.mdc)
13
11
 
14
- ## iOS: зазвичай лише SPM, виняток Podfile
12
+ [capacitor-ios-spm](./js/ios_spm.mdc)
15
13
 
16
- - **Правило за замовчуванням:** не залишай `Podfile` (поза `Pods/`) у вихідному iOS-шарі, **якщо** уся потрібна
17
- iOS-функціональність (нативні плагіни/модулі) може працювати **лише** через **SPM** (Swift Package Manager).
14
+ ## Швидкий gate через conftest
18
15
 
19
- - **Плагіни зі скоупу @nitra/:** за політикою вони **підтримуюють SPM**; **перевіряти** їх на **SPM** **не
20
- потрібно** (і **check** цього **не** робить — **немає** обходу `package.json` на предмет **@nitra/**).
16
+ | namespace | що перевіряє |
17
+ |---|---|
18
+ | `capacitor.package_json` | версія `@capacitor/core` у `dependencies` — мінімум major 8 (спрощений JS-порт для одиничного `package.json`) |
21
19
 
22
- - **Коли `Podfile` дозволений:** якщо **не** вся потрібна iOS-функціональність **поза** **@nitra/** (сторонні
23
- **Capacitor**-плагіни, інша нативна залежність) **доступна** через **SPM** — `Podfile` **дозволяється**,
24
- але це **обов’язково** треба явно задати в кореневому **`package.json`** або в
25
- **`capacitor.config.json` / `capacitor.config.ts` / `capacitor.config.mjs`**
26
-
27
- - **`"iosCocoaPodsBecausePluginsLackSpm": true`** (семантика: **не** **вся** потрібна нативна частина
28
- **поза** **@nitra/** **на** **SPM**; **@nitra/** у це **не** входить);
29
- - або **`"iosCocoaPodsAllowed": true`** (короткий **alias** для того самого **винятку**);
30
-
31
- Без **одного** з цих прапорів `true` наявний **Podfile** поза **`Pods/`** вважається **порушенням** правила **«лише** **SPM**».
32
-
33
- - Перевірка читає **лише** кореневі файли: **`package.json`**, потім **capacitor-конфіги** у **корені** (див. вище).
34
- У **`.ts` / `.mjs`**: шукається блок **nitra** `{ ... }` і **на його тілі** перевіряються ці **boolean**-поля.
20
+ JS-перевірка (`platforms.mjs`) authoritative: підтримує `||`-діапазони, hyphen-range, workspace-monorepo та iOS-специфічну логіку (Podfile).
@@ -0,0 +1,15 @@
1
+ ## STOP — перед завершенням відповіді агента
2
+
3
+ > **Якщо в цій сесії ти змінив(ла) файли в пакетному workspace** (код, rego, правила, скіли, скрипти, конфіги, тести — **не** лише `docs/` / `doc/`) — **не завершуй задачу**, поки не виконаєш **усі три** кроки нижче в **тому ж** наборі змін. Це не «опційно після синку» — це частина PR.
4
+
5
+ 1. **Поклади change-файл** `<ws>/.changes/YYMMDD-HHMM.md` з frontmatter `bump:` (`major|minor|patch`) + `section:` (`Added|Changed|Fixed|Removed`) і текстом опису. Команда: `npx @nitra/cursor change --bump <…> --section <…> --message "<…>" [--ws <шлях>]`. Якщо файл за ту саму хвилину вже існує, CLI атомарно створює `YYMMDD-HHMM-2.md`, потім `-3` тощо.
6
+ 2. **Ніколи** не редагуй `version` і `CHANGELOG.md` вручну — навіть для hotfix. Єдиний артефакт зміни — change-файл; `version`/CHANGELOG формує `n-cursor release` у CI на `main` (агрегує change-файли, ставить git-тег `<name>@<version>`). Будь-яка зміна `version` поза CI (drift від бази чи опублікованої) завалює `check changelog` — навіть якщо поряд є change-файл.
7
+ 3. **`npx @nitra/cursor fix changelog`** → exit **`0`** (достатньо наявності change-файлу; `version` лишається незмінним).
8
+
9
+ **Це правило релевантне завжди після будь-якої зміни** коду/конфігу/тестів/воркспейсу — **не** класифікуй його як «лише для релізу». Уже запущені тести чи білд **не** знімають вимогу changelog. Операційно перед фінальною відповіддю: `git status --short` → зіставити змінені шляхи з workspace-ами → change-файл для кожного релевантного workspace → `npx @nitra/cursor fix changelog` (exit `0`). Якщо у фінальній відповіді не можеш заповнити рядок `Changelog: …`, отже ти пропустив(ла) цей крок.
10
+
11
+ **Тригер шляхів (приклади):** `npm/**`, `packages/foo/**`, будь-який каталог з власним `package.json` / `pyproject.toml`, куди потрапили правки.
12
+
13
+ **Інверсія (change-файл не потрібен):** лише `docs/` / `doc/`; синхронізований із `@nitra/cursor` інструментарій (`.cursor/`, `.claude/`); лише `.gitignore`. **Корінь монорепо** (воркспейс `.` за наявності підпакетів) не перевіряється взагалі — отже й кореневі `AGENTS.md` / `CLAUDE.md` та bump `@nitra/cursor` у `devDependencies`. Окремого «релізного кроку» у feature-флоу немає — `version`/`CHANGELOG.md` змінює лише CI.
14
+
15
+ **Pre-commit (людина):** `hk` у цьому репо запускає `fix changelog` при змінах під `npm/**` в **autofix-режимі** (env `N_CURSOR_CHANGELOG_AUTOFIX=1`): за відсутності change-файлу хук **сам** його створює (дефолти `patch`/`Changed`, опис = subject останнього коміту) і ставить у git-індекс, тож коміт не падає. Це лише підстраховка — агент не покладайся на неї: виконуй кроки 1–3 **до** фінальної відповіді, бо автоген ставить осмислений опис лише випадково (subject попереднього коміту), і `bump`/`section`/текст майже завжди треба відредагувати. Autofix-режим також **не робить мережевих викликів** (`npm view` / PyPI fetch пропускаються) — реєстрова drift-перевірка `version` у хуці не виконується (її ловить CI / ручний `fix`). Поза хуком (CI, ручний `fix`/`check`) autofix вимкнено — поведінка лишається fail-on-missing з повною drift-перевіркою.
@@ -0,0 +1,33 @@
1
+ ## Формат CHANGELOG.md
2
+
3
+ [Keep a Changelog 1.1.0](https://keepachangelog.com/uk/1.1.0/), мова — українська, новіші версії зверху.
4
+
5
+ ```md title="<ws>/CHANGELOG.md"
6
+ # Changelog
7
+
8
+ Усі помітні зміни цього пакета документуються тут.
9
+
10
+ Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
11
+
12
+ ## [1.2.3] - 2026-05-05
13
+
14
+ ### Added
15
+
16
+ - ...
17
+
18
+ ### Changed
19
+
20
+ - ...
21
+
22
+ ### Fixed
23
+
24
+ - ...
25
+ ```
26
+
27
+ Секції — підмножина `### Added`, `### Changed`, `### Fixed`, `### Removed` (одна або кілька).
28
+
29
+ ## Post-release інваріант (гарантує CI)
30
+
31
+ Перша (верхня) секція `## [version]` у `CHANGELOG.md` дорівнює полю `version` у маніфесті — але це **post-release** твердження, яке забезпечує `n-cursor release` у CI, агрегуючи change-файли (bump `version` + генерація секції + git-тег `<name>@<version>`). **Локально цю рівність руками не підтримують**: у feature-флоу `version`/`CHANGELOG.md` не чіпають, тож верхня секція може відставати від майбутньої версії — це нормально. Drift `version` поза CI (vs реєстр / vs git-база) ловить `check changelog` як заборонений ручний bump.
32
+
33
+ Інструкції щодо bump `version` і редагування `CHANGELOG.md` живуть **лише** в правилі `changelog` — джерелі істини. Інші правила (зокрема `n-npm-module.mdc`) їй підпорядковані й власних інструкцій bump/CHANGELOG не дублюють.
@@ -0,0 +1,40 @@
1
+ ## Дві моделі бази порівняння
2
+
3
+ Режим визначається автоматично з маніфесту.
4
+
5
+ ### registry-published (npm / PyPI)
6
+
7
+ **npm:** непорожнє `name`, не `private: true`, масив `files`.
8
+
9
+ **Python:** статичні `project.name` і `project.version` у `pyproject.toml` (або Poetry-секція).
10
+
11
+ 1. **Локальна `version` ≠ опублікованій** (npm / PyPI): drift поза CI → **fail** (ручний bump заборонено; навіть із change-файлом). Відкоти `version`.
12
+ 2. **Версії збігаються**, але в git є **релевантні** зміни без change-файлу → fail. Для npm `"CHANGELOG.md"` має бути в `files` (публікується разом із пакетом).
13
+ 3. **Реєстр недосяжний** — fail-safe pass.
14
+ 4. **Немає релевантних змін** — pass.
15
+
16
+ ### local-only
17
+
18
+ **npm:** `private: true` або без `files`. **Python:** без пари name+version для реєстру. База залежить від гілки:
19
+
20
+ 1. На **`dev`** local-only не активний (крім незакомічених registry-published).
21
+ 2. На **`main`** — diff від **`origin/main`** (попередній опублікований `main`); без remote — від `HEAD~1`. **`dev` не використовується** як база на `main`.
22
+ 3. На **feature-гілці** — `merge-base` з **`dev`**, якщо є; інакше з **`main`** (репо без `dev`).
23
+ 4. Drift `version` від бази → **fail** (ручний bump заборонено). Зміни фіксуй change-файлом; bump зробить CI.
24
+
25
+ Якщо немає git або немає `dev`/`main`/`origin/main` — local-only пропускається.
26
+
27
+ ## Чеклист агента (деталі)
28
+
29
+ Повний алгоритм — у блоці **STOP** (changelog-agent-workflow); тут лише уточнення.
30
+
31
+ **Інверсія (за замовчуванням не вимагають change-файлу):**
32
+
33
+ - зміни **лише** під `docs/` або `doc/`;
34
+ - синхронізований із `@nitra/cursor` інструментарій під `.cursor/` (канонічні правила й скіли) і `.claude/` (ADR-хуки) — це дзеркало tooling-пакета, а не логіка воркспейсу;
35
+ - будь-які зміни в **корені монорепо** (воркспейс `.` за наявності підпакетів) — корінь веде glue/конфіг/tooling, власного CHANGELOG не має; помітні зміни документують підпакети. Сюди потрапляють і кореневі `AGENTS.md` / `CLAUDE.md`, і bump `@nitra/cursor` у `devDependencies`;
36
+ - файли під **`.gitignore`**.
37
+
38
+ **Вимагають change-файл** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо). Виняток `.cursor/` / `.claude/` **не** поширюється на джерело правил у репо `@nitra/cursor` — воно лежить під `npm/`, тож зміни в ньому далі вимагають change-файлу.
39
+
40
+ Перевірка програмна (`changelog/js/consistency.mjs`).
@@ -4,104 +4,10 @@ version: '3.4'
4
4
  alwaysApply: true
5
5
  ---
6
6
 
7
- ## STOPперед завершенням відповіді агента
7
+ У кожному **пакетному** workspace (каталог із `package.json` або `pyproject.toml`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** кожен пакет веде свій. Маніфест версії: **JS/Bun/npm** — `package.json` (`version`); **Python** — `pyproject.toml` (`[project].version` або `[tool.poetry].version`).
8
8
 
9
- > **Якщо в цій сесії ти змінив(ла) файли в пакетному workspace** (код, rego, правила, скіли, скрипти, конфіги, тести — **не** лише `docs/` / `doc/`) — **не завершуй задачу**, поки не виконаєш **усі три** кроки нижче в **тому ж** наборі змін. Це не «опційно після синку» — це частина PR.
9
+ [changelog-agent-workflow](./js/agent-workflow.mdc)
10
10
 
11
- 1. **Поклади change-файл** `<ws>/.changes/YYMMDD-HHMM.md` з frontmatter `bump:` (`major|minor|patch`) + `section:` (`Added|Changed|Fixed|Removed`) і текстом опису. Команда: `npx @nitra/cursor change --bump <…> --section <…> --message "<…>" [--ws <шлях>]`. Якщо файл за ту саму хвилину вже існує, CLI атомарно створює `YYMMDD-HHMM-2.md`, потім `-3` тощо.
12
- 2. **Ніколи** не редагуй `version` і `CHANGELOG.md` вручну — навіть для hotfix. Єдиний артефакт зміни — change-файл; `version`/CHANGELOG формує `n-cursor release` у CI на `main` (агрегує change-файли, ставить git-тег `<name>@<version>`). Будь-яка зміна `version` поза CI (drift від бази чи опублікованої) завалює `check changelog` — навіть якщо поряд є change-файл.
13
- 3. **`npx @nitra/cursor fix changelog`** → exit **`0`** (достатньо наявності change-файлу; `version` лишається незмінним).
11
+ [changelog-comparison-models](./js/comparison-models.mdc)
14
12
 
15
- **Це правило релевантне завжди після будь-якої зміни** коду/конфігу/тестів/воркспейсу — **не** класифікуй його як «лише для релізу». Уже запущені тести чи білд **не** знімають вимогу changelog. Операційно перед фінальною відповіддю: `git status --short` → зіставити змінені шляхи з workspace-ами → change-файл для кожного релевантного workspace → `npx @nitra/cursor fix changelog` (exit `0`). Якщо у фінальній відповіді не можеш заповнити рядок `Changelog: …`, отже ти пропустив(ла) цей крок.
16
-
17
- **Тригер шляхів (приклади):** `npm/**`, `packages/foo/**`, будь-який каталог з власним `package.json` / `pyproject.toml`, куди потрапили правки.
18
-
19
- **Інверсія (change-файл не потрібен):** лише `docs/` / `doc/`; синхронізований із `@nitra/cursor` інструментарій (`.cursor/`, `.claude/`); лише `.gitignore`. **Корінь монорепо** (воркспейс `.` за наявності підпакетів) не перевіряється взагалі — отже й кореневі `AGENTS.md` / `CLAUDE.md` та bump `@nitra/cursor` у `devDependencies`. Окремого «релізного кроку» у feature-флоу немає — `version`/`CHANGELOG.md` змінює лише CI.
20
-
21
- **Pre-commit (людина):** `hk` у цьому репо запускає `fix changelog` при змінах під `npm/**` в **autofix-режимі** (env `N_CURSOR_CHANGELOG_AUTOFIX=1`): за відсутності change-файлу хук **сам** його створює (дефолти `patch`/`Changed`, опис = subject останнього коміту) і ставить у git-індекс, тож коміт не падає. Це лише підстраховка — агент не покладайся на неї: виконуй кроки 1–3 **до** фінальної відповіді, бо автоген ставить осмислений опис лише випадково (subject попереднього коміту), і `bump`/`section`/текст майже завжди треба відредагувати. Autofix-режим також **не робить мережевих викликів** (`npm view` / PyPI fetch пропускаються) — реєстрова drift-перевірка `version` у хуці не виконується (її ловить CI / ручний `fix`). Поза хуком (CI, ручний `fix`/`check`) autofix вимкнено — поведінка лишається fail-on-missing з повною drift-перевіркою.
22
-
23
- ---
24
-
25
- У кожному **пакетному** workspace (каталог із `package.json` або `pyproject.toml`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** — кожен пакет веде свій.
26
-
27
- **Маніфест версії:**
28
-
29
- - **JS / Bun / npm** — `<ws>/package.json` (`version`, для монорепо ще `workspaces` у кореневому `package.json`).
30
- - **Python** — `<ws>/pyproject.toml`: `[project].name` / `[project].version` (PEP 621) або `[tool.poetry].name` / `[tool.poetry].version`.
31
-
32
- Каталоги лише з `pyproject.toml` (без `package.json`) теж враховуються; `node_modules/`, `.venv/`, `venv/` при пошуку ігноруються.
33
-
34
- ## Чеклист агента (деталі)
35
-
36
- Повний алгоритм — у блоці **STOP** вище; тут лише уточнення.
37
-
38
- **Інверсія (за замовчуванням не вимагають change-файлу):**
39
-
40
- - зміни **лише** під `docs/` або `doc/`;
41
- - синхронізований із `@nitra/cursor` інструментарій під `.cursor/` (канонічні правила та скіли) і `.claude/` (ADR-хуки) — це дзеркало tooling-пакета, а не логіка воркспейсу;
42
- - будь-які зміни в **корені монорепо** (воркспейс `.` за наявності підпакетів) — корінь веде glue/конфіг/tooling, власного CHANGELOG не має; помітні зміни документують підпакети. Сюди потрапляють і кореневі `AGENTS.md` / `CLAUDE.md`, і bump `@nitra/cursor` у `devDependencies`;
43
- - файли під **`.gitignore`**.
44
-
45
- **Вимагають change-файл** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо). Виняток `.cursor/` / `.claude/` **не** поширюється на джерело правил у репо `@nitra/cursor` — воно лежить під `npm/`, тож зміни в ньому далі вимагають change-файлу.
46
-
47
- Перевірка програмна (`changelog/js/consistency.mjs`).
48
-
49
- ## Дві моделі бази порівняння
50
-
51
- Режим визначається автоматично з маніфесту.
52
-
53
- ### registry-published (npm / PyPI)
54
-
55
- **npm:** непорожнє `name`, не `private: true`, масив `files`.
56
-
57
- **Python:** статичні `project.name` і `project.version` у `pyproject.toml` (або Poetry-секція).
58
-
59
- 1. **Локальна `version` ≠ опублікованій** (npm / PyPI): drift поза CI → **fail** (ручний bump заборонено; навіть із change-файлом). Відкоти `version`.
60
- 2. **Версії збігаються**, але в git є **релевантні** зміни без change-файлу → fail. Для npm `"CHANGELOG.md"` має бути в `files` (публікується разом із пакетом).
61
- 3. **Реєстр недосяжний** — fail-safe pass.
62
- 4. **Немає релевантних змін** — pass.
63
-
64
- ### local-only
65
-
66
- **npm:** `private: true` або без `files`. **Python:** без пари name+version для реєстру. База залежить від гілки:
67
-
68
- 1. На **`dev`** local-only не активний (крім незакомічених registry-published).
69
- 2. На **`main`** — diff від **`origin/main`** (попередній опублікований `main`); без remote — від `HEAD~1`. **`dev` не використовується** як база на `main`.
70
- 3. На **feature-гілці** — `merge-base` з **`dev`**, якщо є; інакше з **`main`** (репо без `dev`).
71
- 4. Drift `version` від бази → **fail** (ручний bump заборонено). Зміни фіксуй change-файлом; bump зробить CI.
72
-
73
- Якщо немає git або немає `dev`/`main`/`origin/main` — local-only пропускається.
74
-
75
- ## Формат CHANGELOG.md
76
-
77
- [Keep a Changelog 1.1.0](https://keepachangelog.com/uk/1.1.0/), мова — українська, новіші версії зверху.
78
-
79
- ```md title="<ws>/CHANGELOG.md"
80
- # Changelog
81
-
82
- Усі помітні зміни цього пакета документуються тут.
83
-
84
- Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
85
-
86
- ## [1.2.3] - 2026-05-05
87
-
88
- ### Added
89
-
90
- - ...
91
-
92
- ### Changed
93
-
94
- - ...
95
-
96
- ### Fixed
97
-
98
- - ...
99
- ```
100
-
101
- Секції — підмножина `### Added`, `### Changed`, `### Fixed`, `### Removed` (одна або кілька).
102
-
103
- ## Post-release інваріант (гарантує CI)
104
-
105
- Перша (верхня) секція `## [version]` у `CHANGELOG.md` дорівнює полю `version` у маніфесті — але це **post-release** твердження, яке забезпечує `n-cursor release` у CI, агрегуючи change-файли (bump `version` + генерація секції + git-тег `<name>@<version>`). **Локально цю рівність руками не підтримують**: у feature-флоу `version`/`CHANGELOG.md` не чіпають, тож верхня секція може відставати від майбутньої версії — це нормально. Drift `version` поза CI (vs реєстр / vs git-база) ловить `check changelog` як заборонений ручний bump.
106
-
107
- Інструкції щодо bump `version` і редагування `CHANGELOG.md` живуть **лише** в цьому правилі — джерелі істини. Інші правила (зокрема `n-npm-module.mdc`) їй підпорядковані й власних інструкцій bump/CHANGELOG не дублюють.
13
+ [changelog-format](./js/changelog-format.mdc)
@@ -0,0 +1,31 @@
1
+ ## Конфіг marksman LSP (`.marksman.toml`)
2
+
3
+ Правило перевіряє наявність `.marksman.toml` у корені проєкту. Якщо файл відсутній — автоматично створює його з canonical baseline, що постачається разом із пакетом (`js/data/marksman_config/marksman.baseline.toml`). Повторні прогони ідемпотентні: існуючий файл не перетирається, ручні правки зберігаються.
4
+
5
+ ### Baseline-конфіг
6
+
7
+ Canonical baseline ([data/marksman_config/marksman.baseline.toml](./data/marksman_config/marksman.baseline.toml)) містить такі налаштування:
8
+
9
+ ```toml
10
+ [core]
11
+ markdown.file_extensions = ["md", "markdown"]
12
+ markdown.glfm = true # GitHub-Flavored Markdown (таблиці, todo, alerts)
13
+
14
+ [completion]
15
+ wiki.style = "file-stem" # [[oidc-pkce-flow]] → docs/adr/oidc-pkce-flow.md
16
+
17
+ [code_action]
18
+ toc.enable = true # «Insert/Update TOC» для довгих arc42-сторінок
19
+ ```
20
+
21
+ **Чому `file-stem`?** ADR-slug збігається з іменем файлу — стабільний ідентифікатор у AUTOGEN `sources`, `manifest.json` і валідаторі. Якщо заголовок ADR змінюється, посилання `[[wiki-link]]` не ламається. Без явного конфіга marksman використовує `title-slug-ref` і вимкнений GLFM — частина задокументованої навігації працювала б інакше.
22
+
23
+ ### Поведінка перевірки
24
+
25
+ | Стан | Результат |
26
+ | ----------------------------- | -------------------------------------------------- |
27
+ | baseline-файл пакета відсутній | `fail` — перевстанови `@nitra/cursor` |
28
+ | `.marksman.toml` вже існує | `pass` — файл не чіпається |
29
+ | `.marksman.toml` відсутній | створює файл з baseline, `pass` |
30
+
31
+ Перевірка запускається як `check`-поверхня; `lint`-поверхні правило не має.
@@ -0,0 +1,33 @@
1
+ ## Рекомендовані розширення VSCode (`.vscode/extensions.json`)
2
+
3
+ Правило перевіряє, що `.vscode/extensions.json` містить усі обовʼязкові записи з канонічного шаблону. Перевірка реалізована через Rego (пакет `ci4.vscode_extensions`) і запускається `conftest` у фазі `check`.
4
+
5
+ ### Канонічний шаблон
6
+
7
+ Канон надходить через `--data` як `{ "template": { "snippet": ... } }`. Поточний snippet ([policy/vscode_extensions/template/extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)):
8
+
9
+ ```json
10
+ {
11
+ "recommendations": ["arr.marksman"]
12
+ }
13
+ ```
14
+
15
+ `arr.marksman` — офіційне VSCode-розширення marksman LSP. Дає ту саму навігацію `cmd+click` / `[[wiki-link]]` / find-references / refactor-перейменування, що й Zed built-in marksman, для контрибʼюторів поза Zed.
16
+
17
+ ### Rego-логіка
18
+
19
+ Для кожного запису з `data.template.snippet.recommendations` перевіряє, що він присутній у `input.recommendations`. Якщо запис відсутній — deny з повідомленням:
20
+
21
+ ```
22
+ .vscode/extensions.json: recommendations має містити "arr.marksman" (ci4.mdc)
23
+ ```
24
+
25
+ ### Приклад коректного файлу
26
+
27
+ ```json title=".vscode/extensions.json"
28
+ {
29
+ "recommendations": ["arr.marksman"]
30
+ }
31
+ ```
32
+
33
+ Інші розширення в масиві `recommendations` дозволені — перевірка лише на наявність обовʼязкових, без виключення додаткових.
@@ -4,9 +4,7 @@ alwaysApply: true
4
4
  version: '3.2'
5
5
  ---
6
6
 
7
- Архітектурна документація проєкту живе у Markdown поряд із кодом. Це не довідник «для людей із порталу архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед будь-якою зміною коду. Тому правила нижче — не оформлення, а робочий процес: який стек використовуємо, як зберігаємо рішення, як автоматично перегенеровуємо проекції з ADR і як рендеримо для змішаної аудиторії (менеджери + інженери + ops).
8
-
9
- Формат самих ADR-файлів — MADR v4.0.0 minimal — описаний у правилі **`adr`** (`npm/rules/adr/adr.mdc`). Тут описано, як ADR використовуються як вхід для архітектурних проекцій, а не як вони створюються.
7
+ Архітектурна документація проєкту живе у Markdown поряд із кодом — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед будь-якою зміною коду. Правила нижче — не оформлення, а робочий процес: який стек використовуємо, як зберігаємо рішення, як автоматично перегенеровуємо проекції з ADR і як рендеримо для змішаної аудиторії (менеджери + інженери + ops). Формат самих ADR-файлів — MADR v4.0.0 minimal — описаний у правилі **`adr`** (`npm/rules/adr/adr.mdc`).
10
8
 
11
9
  ## Markdown як джерело істини
12
10
 
@@ -208,17 +206,7 @@ User Service відповідає за автентифікацію та про
208
206
  }
209
207
  ```
210
208
 
211
- **`.marksman.toml`** у корені проєкту авто-створюється правилом ci4 при першому `npx @nitra/cursor fix ci4` із canonical baseline ([data/marksman_config/marksman.baseline.toml](./js/data/marksman_config/marksman.baseline.toml)). Ключові опції`markdown.glfm = true` (потрібен для GFM-alerts/таблиць/todo з portable subset), `[completion] wiki.style = "file-stem"` (ADR slug == ім'я файла, стабільний ідентифікатор у AUTOGEN `sources`/manifest/валідаторі — заголовок змінюється, посилання не ламається), `[code_action] toc.enable = true` (TOC code action для довгих arc42-сторінок). Без явного конфіга marksman використовує `title-slug-ref` і вимкнений GLFM — частина задокументованої навігації працювала б інакше. Ручні правки конфіга не перетираються — `ensureBaselineFile` ідемпотентний.
212
-
213
- **VSCode-альтернатива.** Контрибʼютори, що працюють у VSCode/Cursor замість Zed, отримують той самий шар навігації через офіційне розширення marksman LSP. Канонічний запис у `.vscode/extensions.json`:
214
-
215
- ```json title=".vscode/extensions.json"
216
- {
217
- "recommendations": ["arr.marksman"]
218
- }
219
- ```
220
-
221
- Канон `recommendations` (substring requirement): [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json). Інші marksman-сумісні редактори (Neovim, Helix, Emacs) налаштовують `marksman` як LSP-сервер за документацією свого редактора — поведінка ідентична (cmd+click по `[link](file.md)`/`[[wiki-link]]`, find-references, refactor-перейменування).
209
+ Деталі конфігурації `.marksman.toml` та VSCode-альтернативи — у відповідних секціях нижче.
222
210
 
223
211
  **Portable-only синтаксис.** Усе, що пишемо в `docs/`, обмежене **CommonMark + GFM + Mermaid у fenced code (` ```mermaid `) + KaTeX (`$...$`) + нативний HTML5 `<details>`**. Заборонено:
224
212
 
@@ -375,3 +363,15 @@ ADR (`docs/adr/<slug>.md`) — джерело правди для autogen-про
375
363
  ## Query mode як бонус
376
364
 
377
365
  Поверх проекцій — інтерактивний RAG над clean ADR. Інженер питає: «чому ми обрали Percona замість MariaDB?». LLM шукає по semantic index `Context and Problem Statement + Decision Outcome`, повертає `docs/adr/<slug>.md` з прямою цитатою. Корпус малий (десятки-сотні clean ADR після normalize) і структурований (фіксовані MADR-заголовки) — дешево й точно, без галюцинацій.
366
+
367
+ ## Швидкий gate через conftest
368
+
369
+ Rego-перевірки, що запускаються через `conftest` у фазі `check`:
370
+
371
+ | Пакет | Що перевіряє |
372
+ | --------------------------- | -------------------------------------------------------------------- |
373
+ | `ci4.vscode_extensions` | `.vscode/extensions.json` містить `arr.marksman` у `recommendations` |
374
+
375
+ [ci4-marksman_config](./js/marksman_config.mdc)
376
+
377
+ [ci4-vscode_extensions](./js/vscode_extensions.mdc)
@@ -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` теж зайвий — база вже його оголошує)