@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.8.7] - 2026-06-22
4
+
5
+ ### Changed
6
+
7
+ - ✨ feat(rules/abie): http_route_base.mdc + активація + ua-домени у ua_http_route.mdc
8
+
3
9
  ## [12.8.6] - 2026-06-22
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.8.6",
3
+ "version": "12.8.7",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,32 @@
1
+ ## JS-перевірки хуків і конфігурації (`hooks.mjs`)
2
+
3
+ `js/hooks.mjs` перевіряє відповідність проєкту правилу `adr` через шість незалежних перевірок:
4
+
5
+ ### 1. Канонічність скриптів хуків
6
+
7
+ Для кожного з двох bash-скриптів (`capture-decisions.sh`, `normalize-decisions.sh`) перевіряє:
8
+
9
+ - файл `.claude/hooks/<script>.sh` **існує** у проєкті;
10
+ - його вміст **ідентичний** bundled-версії з пакета `@nitra/cursor`.
11
+
12
+ Якщо файл відсутній або відрізняється — `fail` з підказкою `npx @nitra/cursor`.
13
+
14
+ ### 2. Наявність `.claude/settings.json`
15
+
16
+ Перевіряє **існування** project-shared файлу `.claude/settings.json`. Структуру хуків всередині (наявність `hooks.Stop[]` з відповідними маркерами) валідує окрема Rego-полісі `adr.settings_json`.
17
+
18
+ ### 3. Cursor hooks config
19
+
20
+ Читає `.cursor/hooks.json` і перевіряє, чи кожен зі скриптів (`capture-decisions.sh`, `normalize-decisions.sh`) присутній у `hooks.stop[*].command` як підрядок-маркер.
21
+
22
+ ### 4. `.gitignore` для лог-файлів хуків
23
+
24
+ Перевіряє, чи покриває `.gitignore` лог-файли `.claude/hooks/capture-decisions.log` і `.claude/hooks/normalize-decisions.log`. Допустимі форми покриття:
25
+
26
+ - точний шлях (`.claude/hooks/capture-decisions.log`)
27
+ - glob `.claude/hooks/*.log` або `.claude/hooks/**/*.log`
28
+ - широкий glob `*.log` або `**/*.log`
29
+
30
+ ### 5. Доступність LLM CLI
31
+
32
+ Інформативна (завжди `pass`) перевірка наявності хоча б одного з `claude` або `cursor-agent` у `PATH`. Якщо жодного немає — хук просто мовчки no-op'ає, тому це попередження, а не помилка.
@@ -0,0 +1,96 @@
1
+ ## MADR v4 і дві фази
2
+
3
+ ADR живуть у єдиному каталозі **`docs/adr/`**. Clean ADR-и мають формат **MADR v4.0.0 minimal** з **OKF v0.1 frontmatter** і точними section headings англійською:
4
+
5
+ - `## Context and Problem Statement`
6
+ - `## Considered Options`
7
+ - `## Decision Outcome`
8
+ - `### Consequences`
9
+ - `## More Information`
10
+
11
+ Вміст секцій — українською, code identifiers / paths / commands — як у transcript. Якщо transcript не містить альтернатив або підтверджених наслідків, hook має явно писати `Інші варіанти в transcript не обговорювалися.` або `transcript не містить підтвердження ...`, а не вигадувати відсутні факти.
12
+
13
+ Є два стани файлу, які відрізняються YAML frontmatter:
14
+
15
+ - **Draft** — файл з frontmatter `session: …`, `captured: …`, `transcript: …` та timestamp-іменем `YYMMDD-HHMM-<sid>.md`. Пише `capture-decisions.sh` після кожної сесії.
16
+ - **Clean** — файл з OKF v0.1 frontmatter (`type: ADR`, `title:`, `description:`) і kebab-case-іменем. Заголовок `#` у тілі не потрібен — `title:` у frontmatter вже є заголовком. `normalize-decisions.sh` зберігає timestamp-префікс чернетки → `YYMMDD-HHMM-<slug>.md` (наприклад `260518-0928-ланцюжок-запуску-abie.md`); створений руками clean-файл може мати просто `<slug>.md`.
17
+
18
+ `normalize-decisions.sh` ніколи не чіпає clean-файли — крім випадку `merge-into`, коли дописує `## Update YYYY-MM-DD` в кінець наявного clean-файлу.
19
+
20
+ ### Фаза 1 — Capture
21
+
22
+ Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сесії (через `jq`), витягає текст, `thinking`-блоки та назви `tool_use`-викликів, передає компактний дайджест у LLM CLI з evidence-bound промптом і записує результат у **`docs/adr/<timestamp>-<session>.md`**, якщо модель повернула MADR-блок з шапкою `## ADR …`. Якщо модель повернула `NONE` (тривіальна сесія) — нічого не пишеться. Рекурсію з внутрішнього виклику моделі блокує env-var `CAPTURE_DECISIONS_RUNNING=1`.
23
+
24
+ Для Cursor payload скрипт бере `transcript_path`, `conversation_id` / `generation_id` і `workspace_roots[0]`; для Claude Code — `transcript_path`, `session_id` і `CLAUDE_PROJECT_DIR`.
25
+
26
+ **Tooling-only skip:** перед викликом LLM `capture-decisions.sh` дивиться у transcript на `tool_use`-правки (`Edit`/`Write`/`MultiEdit`). Якщо всі змінені файли потрапляють у вузький allowlist — `.cspell.json`, `docs/adr/*.md`, `CHANGELOG.md`, кореневі `AGENTS.md`/`CLAUDE.md`, або `package.json` з diff виключно по ключу `version` — хук виходить з `exit 0` без LLM-виклику. Це розриває петлю «`/n-lint` править `.cspell.json` → з'являється новий ADR-draft → наступний `/n-lint` знов псує правопис у цьому draft». Поведінку вимикає `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0`.
27
+
28
+ ### Фаза 2 — Normalize
29
+
30
+ Stop-hook `normalize-decisions.sh` спрацьовує на тому самому `Stop`-евенті, але:
31
+
32
+ - Виходить миттєво, якщо чернеток (`session:` у frontmatter) менше ніж **`ADR_NORMALIZE_THRESHOLD`** (default 30).
33
+ - Виходить миттєво, якщо від попередньої спроби пройшло менше **`ADR_NORMALIZE_MIN_INTERVAL_HOURS`** годин (default 6) — щоб не крутитися щоразу, коли поріг постійний.
34
+ - Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 10, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
35
+ - Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
36
+ - Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
37
+ - Перед викликом LLM для кожної чернетки batch'а читає `transcript:` із frontmatter і той самий tool_use-список. Чернетки tooling-only — видаляє без виклику LLM. Якщо після фільтра batch порожній — `exit 0`.
38
+
39
+ LLM повертає масив операцій:
40
+
41
+ | `op` | Семантика | Поля |
42
+ | --- | --- | --- |
43
+ | `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
44
+ | `rewrite` | Чернетка стає окремим clean-файлом MADR v4 minimal: frontmatter знімається, ім'я → `<timestamp>-<slug>.md` (timestamp-префікс чернетки збережено), додаються `**Status:** Accepted`, `**Date:**` з `captured` і canonical MADR headings. | `file`, `slug`, `content` |
45
+ | `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
46
+
47
+ `slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. До імені clean-файлу скрипт додає `YYMMDD-HHMM-` чернетки, тож запис лишається прив'язаним до часу capture, а `docs/adr/` сортується хронологічно. Колізія імен обробляється детермінованим суфіксом `-2`, `-3`. Старі чернетки з `YYYYMMDD-HHMMSS-` prefix нормалізатор також розпізнає, щоб не ламати наявний inbox.
48
+
49
+ ### Жодних git-операцій
50
+
51
+ `normalize-decisions.sh` **не комітить, не `git add`, нічого з git**. Усі зміни — у робочому дереві. Розробник у зручний момент дивиться `git status` / `git diff` і вирішує: `git add` + commit, `git checkout -- <file>` для відкату, або правки руками. Це і є review-вікно.
52
+
53
+ ### Recursion guard і ENV-керування
54
+
55
+ Інший LLM CLI, який запустить normalize, успадковує `ADR_NORMALIZE_RUNNING=1` — внутрішній Stop-hook вийде відразу. Доступні ENV:
56
+
57
+ | Змінна | Default | Призначення |
58
+ | --- | --- | --- |
59
+ | `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
60
+ | `ADR_NORMALIZE_BATCH` | `10` | Максимум чернеток у одному виклику LLM. |
61
+ | `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
62
+ | `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
63
+ | `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
64
+ | `ADR_NORMALIZE_CURSOR_MODEL` | `claude-4.6-sonnet-medium` | Модель для `cursor-agent -p`. |
65
+ | `ADR_NORMALIZE_SKIP_TOOLING_ONLY` | `1` | `0` — вимкнути structural skip tooling-only сесій (старий behavior). |
66
+
67
+ Для ручного запуску (поза порогом і поза Stop-хуком) є **`/n-adr-normalize`** — slash-команда тимчасово виставляє `ADR_NORMALIZE_THRESHOLD=0` і `ADR_NORMALIZE_MIN_INTERVAL_HOURS=0` та викликає скрипт напряму.
68
+
69
+ ## LLM CLI: claude → cursor-agent fallback
70
+
71
+ Обидва скрипти обирають доступний CLI (порядок фіксований):
72
+
73
+ 1. **`claude`** (Anthropic Claude Code CLI) — `claude -p --model "<model>"`.
74
+ 2. **`cursor-agent`** (Cursor IDE CLI) — `cursor-agent -p --mode ask --output-format text --model "<model>"`.
75
+ 3. Жодного CLI у `PATH` — скрипт виходить з кодом `0` і нічого не пише.
76
+
77
+ `--mode ask` для cursor-agent навмисно: режим Q&A read-only, без shell/edit-інструментів — для класифікації / нормалізації інструменти не потрібні.
78
+
79
+ ## Структура каталогу
80
+
81
+ ```text
82
+ docs/adr/
83
+ ├── YYMMDD-HHMM-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
84
+ └── YYMMDD-HHMM-<slug>.md # clean ADR-и (без frontmatter, timestamp-префікс чернетки збережено)
85
+ .claude/hooks/
86
+ ├── capture-decisions.sh # auto-synced з пакета
87
+ ├── normalize-decisions.sh # auto-synced з пакета
88
+ ├── capture-decisions.log # лог запусків capture (НЕ коміти)
89
+ ├── normalize-decisions.log # лог запусків normalize (НЕ коміти)
90
+ ├── .normalize-state # timestamp останнього normalize-запуску (НЕ коміти)
91
+ └── .normalize.lock # lock-файл (НЕ коміти)
92
+ .cursor/
93
+ └── hooks.json # Cursor Agent stop-hooks для тих самих скриптів
94
+ ```
95
+
96
+ `.gitignore` у корені проєкту повинен містити базові рядки (`node_modules/`, `dist/`, `*.secret`) і патерни для ADR Stop-hook (**`.claude/hooks/*.log`**, `.claude/hooks/.normalize-state`, `.claude/hooks/.normalize.lock`). Канонічний фрагмент (дописується `npx @nitra/cursor`, коли правило `adr` увімкнене): [.gitignore.snippet](./templates/hooks/.gitignore.snippet).
@@ -0,0 +1,34 @@
1
+ ## Rego-перевірки конфігурації settings.json
2
+
3
+ ### `adr.settings_json` — Stop-хуки у `.claude/settings.json`
4
+
5
+ Пакет перевіряє, що project-shared `.claude/settings.json` містить **обидві** managed Stop-hook групи (ідентифікуються підрядком у `command`):
6
+
7
+ | Маркер | Призначення |
8
+ | --- | --- |
9
+ | `.claude/hooks/capture-decisions.sh` | Capture-хук — запис чернеток ADR після сесії |
10
+ | `.claude/hooks/normalize-decisions.sh` | Normalize-хук — батч-нормалізація чернеток |
11
+
12
+ Помилка, якщо `hooks.Stop[*].hooks[*].command` не містить маркера для будь-якого зі скриптів.
13
+
14
+ Запуск локально:
15
+
16
+ ```bash
17
+ conftest test .claude/settings.json -p npm/rules/adr/policy \
18
+ --namespace adr.settings_json
19
+ ```
20
+
21
+ ### `adr.settings_local_json` — відсутність дублів у `.claude/settings.local.json`
22
+
23
+ Пакет перевіряє **зворотне**: якщо `.claude/settings.local.json` існує, він **не повинен** містити жодного зі Stop-хуків `capture-decisions.sh` або `normalize-decisions.sh` — інакше кожен скрипт виконується **двічі** на одну Stop-подію.
24
+
25
+ Помилка, якщо `hooks.Stop[*].hooks[*].command` знаходить маркер у local-файлі.
26
+
27
+ Запуск локально:
28
+
29
+ ```bash
30
+ conftest test .claude/settings.local.json -p npm/rules/adr/policy \
31
+ --namespace adr.settings_local_json
32
+ ```
33
+
34
+ > Якщо хук колись був у `.claude/settings.local.json` — прибери дубль вручну після того, як переніс його до project-shared `settings.json`.
@@ -4,102 +4,9 @@ alwaysApply: true
4
4
  version: '2.2'
5
5
  ---
6
6
 
7
- ## MADR v4 і дві фази
7
+ Правило `adr` автоматично фіксує архітектурні рішення (ADR) після кожної сесії через Stop-хуки і батч-нормалізує чернетки у канонічний формат MADR v4.
8
8
 
9
- ADR живуть у єдиному каталозі **`docs/adr/`**. Clean ADR-и мають формат **MADR v4.0.0 minimal** з **OKF v0.1 frontmatter** і точними section headings англійською:
10
-
11
- - `## Context and Problem Statement`
12
- - `## Considered Options`
13
- - `## Decision Outcome`
14
- - `### Consequences`
15
- - `## More Information`
16
-
17
- Вміст секцій — українською, code identifiers / paths / commands — як у transcript. Якщо transcript не містить альтернатив або підтверджених наслідків, hook має явно писати `Інші варіанти в transcript не обговорювалися.` або `transcript не містить підтвердження ...`, а не вигадувати відсутні факти.
18
-
19
- Є два стани файлу, які відрізняються YAML frontmatter:
20
-
21
- - **Draft** — файл з frontmatter `session: …`, `captured: …`, `transcript: …` та timestamp-іменем `YYMMDD-HHMM-<sid>.md`. Пише `capture-decisions.sh` після кожної сесії.
22
- - **Clean** — файл з OKF v0.1 frontmatter (`type: ADR`, `title:`, `description:`) і kebab-case-іменем. Заголовок `#` у тілі не потрібен — `title:` у frontmatter вже є заголовком. `normalize-decisions.sh` зберігає timestamp-префікс чернетки → `YYMMDD-HHMM-<slug>.md` (наприклад `260518-0928-ланцюжок-запуску-abie.md`); створений руками clean-файл може мати просто `<slug>.md`.
23
-
24
- `normalize-decisions.sh` ніколи не чіпає clean-файли — крім випадку `merge-into`, коли дописує `## Update YYYY-MM-DD` в кінець наявного clean-файлу.
25
-
26
- ### Фаза 1 — Capture
27
-
28
- Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сесії (через `jq`), витягає текст, `thinking`-блоки та назви `tool_use`-викликів, передає компактний дайджест у LLM CLI з evidence-bound промптом і записує результат у **`docs/adr/<timestamp>-<session>.md`**, якщо модель повернула MADR-блок з шапкою `## ADR …`. Якщо модель повернула `NONE` (тривіальна сесія) — нічого не пишеться. Рекурсію з внутрішнього виклику моделі блокує env-var `CAPTURE_DECISIONS_RUNNING=1`.
29
-
30
- Для Cursor payload скрипт бере `transcript_path`, `conversation_id` / `generation_id` і `workspace_roots[0]`; для Claude Code — `transcript_path`, `session_id` і `CLAUDE_PROJECT_DIR`.
31
-
32
- **Tooling-only skip:** перед викликом LLM `capture-decisions.sh` дивиться у transcript на `tool_use`-правки (`Edit`/`Write`/`MultiEdit`). Якщо всі змінені файли потрапляють у вузький allowlist — `.cspell.json`, `docs/adr/*.md`, `CHANGELOG.md`, кореневі `AGENTS.md`/`CLAUDE.md`, або `package.json` з diff виключно по ключу `version` — хук виходить з `exit 0` без LLM-виклику. Це розриває петлю «`/n-lint` править `.cspell.json` → з'являється новий ADR-draft → наступний `/n-lint` знов псує правопис у цьому draft». Поведінку вимикає `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0`.
33
-
34
- ### Фаза 2 — Normalize
35
-
36
- Stop-hook `normalize-decisions.sh` спрацьовує на тому самому `Stop`-евенті, але:
37
-
38
- - Виходить миттєво, якщо чернеток (`session:` у frontmatter) менше ніж **`ADR_NORMALIZE_THRESHOLD`** (default 30).
39
- - Виходить миттєво, якщо від попередньої спроби пройшло менше **`ADR_NORMALIZE_MIN_INTERVAL_HOURS`** годин (default 6) — щоб не крутитися щоразу, коли поріг постійний.
40
- - Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 10, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
41
- - Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
42
- - Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
43
- - Перед викликом LLM для кожної чернетки batch'а читає `transcript:` із frontmatter і той самий tool_use-список. Чернетки tooling-only — видаляє без виклику LLM. Якщо після фільтра batch порожній — `exit 0`.
44
-
45
- LLM повертає масив операцій:
46
-
47
- | `op` | Семантика | Поля |
48
- | --- | --- | --- |
49
- | `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
50
- | `rewrite` | Чернетка стає окремим clean-файлом MADR v4 minimal: frontmatter знімається, ім'я → `<timestamp>-<slug>.md` (timestamp-префікс чернетки збережено), додаються `**Status:** Accepted`, `**Date:**` з `captured` і canonical MADR headings. | `file`, `slug`, `content` |
51
- | `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
52
-
53
- `slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. До імені clean-файлу скрипт додає `YYMMDD-HHMM-` чернетки, тож запис лишається прив'язаним до часу capture, а `docs/adr/` сортується хронологічно. Колізія імен обробляється детермінованим суфіксом `-2`, `-3`. Старі чернетки з `YYYYMMDD-HHMMSS-` prefix нормалізатор також розпізнає, щоб не ламати наявний inbox.
54
-
55
- ### Жодних git-операцій
56
-
57
- `normalize-decisions.sh` **не комітить, не `git add`, нічого з git**. Усі зміни — у робочому дереві. Розробник у зручний момент дивиться `git status` / `git diff` і вирішує: `git add` + commit, `git checkout -- <file>` для відкату, або правки руками. Це і є review-вікно.
58
-
59
- ### Recursion guard і ENV-керування
60
-
61
- Інший LLM CLI, який запустить normalize, успадковує `ADR_NORMALIZE_RUNNING=1` — внутрішній Stop-hook вийде відразу. Доступні ENV:
62
-
63
- | Змінна | Default | Призначення |
64
- | --- | --- | --- |
65
- | `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
66
- | `ADR_NORMALIZE_BATCH` | `10` | Максимум чернеток у одному виклику LLM. |
67
- | `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
68
- | `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
69
- | `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
70
- | `ADR_NORMALIZE_CURSOR_MODEL` | `claude-4.6-sonnet-medium` | Модель для `cursor-agent -p`. |
71
- | `ADR_NORMALIZE_SKIP_TOOLING_ONLY` | `1` | `0` — вимкнути structural skip tooling-only сесій (старий behavior). |
72
-
73
- Для ручного запуску (поза порогом і поза Stop-хуком) є **`/n-adr-normalize`** — slash-команда тимчасово виставляє `ADR_NORMALIZE_THRESHOLD=0` і `ADR_NORMALIZE_MIN_INTERVAL_HOURS=0` та викликає скрипт напряму.
74
-
75
- ## LLM CLI: claude → cursor-agent fallback
76
-
77
- Обидва скрипти обирають доступний CLI (порядок фіксований):
78
-
79
- 1. **`claude`** (Anthropic Claude Code CLI) — `claude -p --model "<model>"`.
80
- 2. **`cursor-agent`** (Cursor IDE CLI) — `cursor-agent -p --mode ask --output-format text --model "<model>"`.
81
- 3. Жодного CLI у `PATH` — скрипт виходить з кодом `0` і нічого не пише.
82
-
83
- `--mode ask` для cursor-agent навмисно: режим Q&A read-only, без shell/edit-інструментів — для класифікації / нормалізації інструменти не потрібні.
84
-
85
- ## Структура каталогу
86
-
87
- ```text
88
- docs/adr/
89
- ├── YYMMDD-HHMM-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
90
- └── YYMMDD-HHMM-<slug>.md # clean ADR-и (без frontmatter, timestamp-префікс чернетки збережено)
91
- .claude/hooks/
92
- ├── capture-decisions.sh # auto-synced з пакета
93
- ├── normalize-decisions.sh # auto-synced з пакета
94
- ├── capture-decisions.log # лог запусків capture (НЕ коміти)
95
- ├── normalize-decisions.log # лог запусків normalize (НЕ коміти)
96
- ├── .normalize-state # timestamp останнього normalize-запуску (НЕ коміти)
97
- └── .normalize.lock # lock-файл (НЕ коміти)
98
- .cursor/
99
- └── hooks.json # Cursor Agent stop-hooks для тих самих скриптів
100
- ```
101
-
102
- `.gitignore` у корені проєкту повинен містити базові рядки (`node_modules/`, `dist/`, `*.secret`) і патерни для ADR Stop-hook (**`.claude/hooks/*.log`**, `.claude/hooks/.normalize-state`, `.claude/hooks/.normalize.lock`). Канонічний фрагмент (дописується `npx @nitra/cursor`, коли правило `adr` увімкнене): [.gitignore.snippet](./js/templates/hooks/.gitignore.snippet).
9
+ [adr-madr-format](./js/madr_format.mdc)
103
10
 
104
11
  ## Stop-hook у `.claude/settings.json`
105
12
 
@@ -161,3 +68,14 @@ Cursor Agent читає project-level **`.cursor/hooks.json`**. `npx @nitra/curs
161
68
  ```
162
69
 
163
70
  Обидва Stop-hook'и ADR живуть у **project-shared** `.claude/settings.json` (закомічений), щоб механізм працював у всіх членів команди. Якщо хук колись був у `.claude/settings.local.json` — прибери дубль вручну: project-shared і local-копія створили б два запуски на одну подію.
71
+
72
+ ## Швидкий gate через conftest
73
+
74
+ | Namespace | Файл | Що перевіряє |
75
+ | --- | --- | --- |
76
+ | `adr.settings_json` | `.claude/settings.json` | Наявність обох Stop-хуків у `hooks.Stop[]` |
77
+ | `adr.settings_local_json` | `.claude/settings.local.json` | Відсутність дублів Stop-хуків у local-файлі |
78
+
79
+ [adr-settings-policy](./js/settings_policy.mdc)
80
+
81
+ [adr-hooks](./js/hooks.mdc)
@@ -0,0 +1,12 @@
1
+ ## Конфігурація bunfig.toml
2
+
3
+ У корені репозиторію має бути **`bunfig.toml`** з **hoisted** лінкером (пласке `node_modules`, сумісне з інструментами, які не розуміють ізольований layout Bun).
4
+
5
+ Канон `bunfig.toml`: [bunfig.toml.snippet.toml](./policy/bunfig/template/bunfig.toml.snippet.toml)
6
+
7
+ ```toml
8
+ [install]
9
+ linker = "hoisted"
10
+ ```
11
+
12
+ Rego-перевірка (`bun.bunfig`) порівнює кожен leaf-ключ у секції із канонічним значенням зі snippet-шаблону. Якщо секція `[install]` відсутня або не є обʼєктом — повідомляє про відсутність секції; якщо значення ключа не збігається — вказує очікуване.
@@ -0,0 +1,60 @@
1
+ ## Структура lockfile та менеджер пакетів
2
+
3
+ Проект використовує **тільки Bun** для керування залежностями та запуску скриптів.
4
+
5
+ **Заборонені lockfile / директорії** (мають бути відсутні у репозиторії):
6
+
7
+ - `package-lock.json`
8
+ - `yarn.lock`
9
+ - `pnpm-lock.yaml`
10
+ - `.yarnrc.yml`
11
+ - `.yarn/`
12
+
13
+ Якщо якийсь із цих файлів чи директорій існує — **видали** його.
14
+
15
+ **Обовʼязковий lockfile:** `bun.lock` (має бути присутній у корені).
16
+
17
+ **Заборонені команди менеджерів пакетів:**
18
+
19
+ - `npm install`, `yarn`, `pnpm` і їх відповідники lockfile (крім `bun.lock`)
20
+
21
+ **Дозволені команди:**
22
+
23
+ - `bun i`
24
+ - `bun run <script>`
25
+ - `bun add <pkg>`
26
+ - `bun add -d <pkg>`
27
+ - `bun remove <pkg>`
28
+ - `bunx <tool>`
29
+ - `npx <tool>` — якщо у проєкті вже використовується `npx` (не замінюй на `bunx`)
30
+
31
+ **Одноразові CLI (`bunx` / `npx`):**
32
+
33
+ - Якщо в проєкті вже використовується **`npx`** (скрипти, CI, Dockerfile) — **залишай `npx`**, не замінюй на `bunx`.
34
+ - Якщо новий виклик CLI додається з нуля і в репозиторії прийнято лише Bun — можна `bunx <tool>`.
35
+
36
+ **Не додавай** у `dependencies` / `devDependencies` пакети, які **використовуються лише як CLI** і їх достатньо викликати через **`bunx <pkg>`** (або **`npx`**, якщо в проєкті так прийнято): наприклад **oxlint**, **jscpd**, **eslint** у корені тощо. Виняток — пакет потрібен як **бібліотека** (імпорт у коді), peer для іншого пакета, або інструмент **не** покривається `bunx` у вашому CI.
37
+
38
+ **Monorepo:**
39
+
40
+ - Встановлювати залежності у відповідному пакеті, а не в корені без потреби.
41
+ - Якщо залежність потрібна лише одному пакету, додавати її в директорії цього пакета.
42
+ - У CI та локально запускати скрипти через `bun run`.
43
+
44
+ **Dockerfile:**
45
+
46
+ ```dockerfile
47
+ FROM oven/bun:alpine AS build-env
48
+ ```
49
+
50
+ замість образу node.
51
+
52
+ **GitHub Actions:** не вставляй у workflow окремі кроки **`actions/setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** та **`bun install`** — їх **заборонено** дублювати в кожному job; завжди використовуй **локальний composite** (деталі й заборона дублювання — **ga.mdc**). Під капотом composite уже містить Node **24**, Bun, кеш і **`bun install --frozen-lockfile`**.
53
+
54
+ Після **`actions/checkout@v6`** (`persist-credentials: false`):
55
+
56
+ ```yaml
57
+ - uses: ./.github/actions/setup-bun-deps
58
+ ```
59
+
60
+ Якщо в репозиторії action збережено під **`./npm/github-actions/setup-bun-deps`**, у `uses:` вкажи цей шлях замість `.github/actions/…` (**ga.mdc**).
@@ -0,0 +1,9 @@
1
+ ## Лінт через n-cursor
2
+
3
+ Лінт запускається через CLI **`n-cursor`**, **не** через `package.json`-скрипти:
4
+
5
+ - **`n-cursor lint --full`** — весь репо: активні у `.n-cursor.json` правила (per-file + full лінтери за `meta.json#lint` scope, конформність) + `oxfmt` у кінці (fix-режим);
6
+ - **`n-cursor lint`** — дельта vs origin (активні у `.n-cursor.json` per-file лінтери лише змінених файлів);
7
+ - **`n-cursor lint <rule…>`** — конкретні правила (лінтер + конформність), напр. **`n-cursor lint ga`**.
8
+
9
+ У кореневому `package.json` **не повинно бути** `lint`/`lint-*` скриптів — єдина точка лінту — CLI `n-cursor`. У CI кожен workflow викликає **`n-cursor lint <rule> --read-only`** напряму (без обгорток).
@@ -0,0 +1,19 @@
1
+ ## Кореневий package.json
2
+
3
+ В кореневому `package.json` не повинно бути `dependencies`, а в `devDependencies` — тільки модулі `@nitra/*`.
4
+
5
+ **Дозволені винятки у `devDependencies` кореня (root-only):**
6
+
7
+ - `vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner` — Vitest/Stryker peer/tools для `n-cursor coverage`
8
+ - `@playwright/test` — для e2e-тестів
9
+
10
+ Тримати їх **у корені** доводиться у будь-якому монорепо-споживачі, бо правило `test` enabled завжди (`test/auto.md` = `завжди`), а класти ці пакети у workspace-и не можна: published пакети (`npm-module.mdc`) не мають `devDependencies`, а інші workspace-и однаково запускають coverage оркестратор з кореня.
11
+
12
+ **Заборонені top-level поля** у root `package.json` (з причинами): [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
13
+
14
+ | Поле | Причина |
15
+ |---|---|
16
+ | `packageManager` | Видали поле — Bun не потребує `packageManager` |
17
+ | `dependencies` | Кореневий `package.json` не повинен містити `dependencies` — додай у workspace-пакети |
18
+
19
+ Якщо в `package.json` є поле `packageManager` — прибрати його, також прибрати всі директорії та файли для yarn.
@@ -7,69 +7,17 @@ version: '2.1'
7
7
 
8
8
  Проект використовує тільки Bun для керування залежностями та запуску скриптів.
9
9
 
10
- **Одноразові CLI (hasura, eslint, vite, cypress тощо):**
10
+ [bun-layout](./js/layout.mdc)
11
11
 
12
- - Якщо в проєкті вже використовується **`npx`** (скрипти, CI, Dockerfile) — **залишай `npx`**, не замінюй на `bunx`.
13
- - Якщо новий виклик CLI додається з нуля і в цьому репозиторії прийнято лише Bun — можна `bunx <tool>`.
12
+ [bun-bunfig](./js/bunfig.mdc)
14
13
 
15
- Заборонено використовувати як менеджер пакетів / lockfile:
14
+ [bun-package_json](./js/package_json.mdc)
16
15
 
17
- - `npm install`, `yarn`, `pnpm` (і відповідні lockfile, крім `bun.lock`)
16
+ [bun-lint](./js/lint.mdc)
18
17
 
19
- Дозволені команди:
18
+ ## Швидкий gate через conftest
20
19
 
21
- - `bun i`
22
- - `bun run <script>`
23
- - `bun add <pkg>`
24
- - `bun add -d <pkg>`
25
- - `bun remove <pkg>`
26
- - `bunx <tool>`
27
- - `npx <tool>`
28
-
29
- **Не додавай** у `dependencies` / `devDependencies` пакети, які **використовуються лише як CLI** і їх достатньо викликати через **`bunx <pkg>`** (або **`npx`**, якщо в проєкті так прийнято): наприклад **oxlint**, **jscpd**, **eslint** у корені тощо. Виняток — пакет потрібен як **бібліотека** (імпорт у коді), peer для іншого пакета, або інструмент **не** покривається `bunx` у вашому CI.
30
-
31
- Lockfile у репозиторії: `bun.lock`.
32
- Не створювати `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`.
33
- Видалити якщо вони є. Видалити .yarn та .yarnrc.yml якщо вони є.
34
-
35
- У корені репозиторію має бути **`bunfig.toml`** з **hoisted** лінкером (пласке `node_modules`, сумісне з інструментами, які не розуміють ізольований layout Bun):
36
-
37
- - Канон `bunfig.toml`: [bunfig.toml.snippet.toml](./policy/bunfig/template/bunfig.toml.snippet.toml)
38
-
39
- Для Bun monorepo:
40
-
41
- - Встановлювати залежності у відповідному пакеті, а не в корені без потреби.
42
- - Якщо залежність потрібна лише одному пакету, додавати її в директорії цього пакета.
43
- - У CI та локально запускати скрипти через `bun run`.
44
-
45
- В кореневому `package.json` не повинно бути `dependencies`, а в `devDependencies` — тільки модулі `@nitra/*`. **Виняток (root-only)** — Vitest/Stryker peer/tools для `n-cursor coverage`: `vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`; а також `@playwright/test` для e2e-тестів. Тримати їх **у корені** доводиться у будь-якому монорепо-споживачі, бо правило `test` enabled завжди (`test/auto.md` = `завжди`), а класти ці пакети у workspace-и не можна: published пакети (`npm-module.mdc`) не мають мати `devDependencies`, а інші workspace-и однаково запускають coverage оркестратор з кореня. Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn.
46
-
47
- - Заборонені top-level поля у root `package.json` (з причинами): [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
48
-
49
- Коли зміна відбувається в Dockerfile, то використовувати
50
-
51
- ```dockerfile
52
- FROM oven/bun:alpine AS build-env
53
- ```
54
-
55
- замість образу node
56
-
57
- У **GitHub Actions** не вставляй у workflow окремі кроки **`actions/setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** та **`bun install`** — їх **заборонено** дублювати в кожному job; завжди використовуй **локальний composite** (деталі й заборона дублювання — **ga.mdc**). Під капотом composite уже містить Node **24**, Bun, кеш і **`bun install --frozen-lockfile`**.
58
-
59
- Після **`actions/checkout@v6`** (`persist-credentials: false`):
60
-
61
- ```yaml
62
- - uses: ./.github/actions/setup-bun-deps
63
- ```
64
-
65
- Якщо в репозиторії action збережено під **`./npm/github-actions/setup-bun-deps`**, у `uses:` вкажи цей шлях замість `.github/actions/…` (**ga.mdc**).
66
-
67
- ## lint
68
-
69
- Лінт запускається через CLI **`n-cursor`**, **не** через `package.json`-скрипти:
70
-
71
- - **`n-cursor lint --full`** — весь репо: активні у `.n-cursor.json` правила (per-file + full лінтери за `meta.json#lint` scope, конформність) + `oxfmt` у кінці (fix-режим);
72
- - **`n-cursor lint`** — дельта vs origin (активні у `.n-cursor.json` per-file лінтери лише змінених файлів);
73
- - **`n-cursor lint <rule…>`** — конкретні правила (лінтер + конформність), напр. **`n-cursor lint ga`**.
74
-
75
- У кореневому `package.json` **не повинно бути** `lint`/`lint-*` скриптів — єдина точка лінту — CLI `n-cursor`. У CI кожен workflow викликає **`n-cursor lint <rule> --read-only`** напряму (без обгорток).
20
+ | Namespace | Що перевіряє |
21
+ |---|---|
22
+ | `bun.bunfig` | Відповідність `bunfig.toml` канонічному шаблону (секції + leaf-ключі) |
23
+ | `bun.package_json` | Заборонені top-level поля у root `package.json`; `devDependencies` лише `@nitra/*` + root-only test peers |
@@ -0,0 +1,69 @@
1
+ ## iOS: лише SPM, виняток через Podfile
2
+
3
+ ### Правило за замовчуванням
4
+
5
+ Не залишай `Podfile` (поза `Pods/`) у вихідному iOS-шарі, **якщо** уся потрібна
6
+ iOS-функціональність (нативні плагіни/модулі) може працювати **лише** через **SPM** (Swift Package Manager).
7
+
8
+ Перевірка рекурсивно шукає `Podfile` у `ios/`, пропускаючи `Pods/`, `build/`, `DerivedData/`.
9
+
10
+ ### Плагіни @nitra/
11
+
12
+ Плагіни зі скоупу `@nitra/` за політикою **підтримують SPM** — перевіряти їх на SPM **не потрібно**
13
+ (check не обходить `package.json` на предмет `@nitra/`).
14
+
15
+ ### Коли Podfile дозволений
16
+
17
+ Якщо не вся потрібна iOS-функціональність поза `@nitra/` (сторонні Capacitor-плагіни, інша
18
+ нативна залежність) доступна через SPM — `Podfile` дозволяється, але це **обов'язково** треба
19
+ явно задати в кореневому **`package.json`** або в **`capacitor.config.json` / `capacitor.config.ts` / `capacitor.config.mjs`**:
20
+
21
+ - **`"iosCocoaPodsBecausePluginsLackSpm": true`** — семантика: не вся потрібна нативна частина
22
+ поза `@nitra/` на SPM; `@nitra/` у це не входить;
23
+ - або **`"iosCocoaPodsAllowed": true`** — короткий alias для того самого винятку.
24
+
25
+ Без одного з цих прапорів `true` наявний `Podfile` поза `Pods/` вважається порушенням правила «лише SPM».
26
+
27
+ **Де задати виняток у `package.json`:**
28
+
29
+ ```json
30
+ {
31
+ "nitra": {
32
+ "iosCocoaPodsBecausePluginsLackSpm": true
33
+ }
34
+ }
35
+ ```
36
+
37
+ або коротший alias:
38
+
39
+ ```json
40
+ {
41
+ "nitra": {
42
+ "iosCocoaPodsAllowed": true
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Де задати виняток у `capacitor.config.json`:**
48
+
49
+ ```json
50
+ {
51
+ "nitra": {
52
+ "iosCocoaPodsAllowed": true
53
+ }
54
+ }
55
+ ```
56
+
57
+ **Де задати виняток у `capacitor.config.ts` / `capacitor.config.mjs`:**
58
+
59
+ ```ts
60
+ const config = {
61
+ // ...
62
+ nitra: {
63
+ iosCocoaPodsAllowed: true,
64
+ },
65
+ }
66
+ ```
67
+
68
+ Перевірка читає **лише** кореневі файли: `package.json`, потім capacitor-конфіги у корені.
69
+ У `.ts` / `.mjs`: шукається блок `nitra { ... }` і на його тілі перевіряються ці boolean-поля.
@@ -0,0 +1,29 @@
1
+ ## Версія Capacitor
2
+
3
+ У `package.json` (у **корені** репозиторію чи **workspace**-пакеті) оголошення **`@capacitor/core`**
4
+ має вказувати діапазон, **сумісний лише з мажорною версією 8 і вище** (наприклад `^8.0.0`).
5
+ **`*`**, `latest` і діапазони, де можлива 7-мажор, — неприйнятні.
6
+
7
+ Перевірка обходить усі `package.json` у дереві (крім `node_modules`, `.git`, `dist`, `coverage`,
8
+ `Pods`, `.turbo`, `.next`, `build`) і перевіряє нижню межу діапазону через `capacitorVersionRangeMinMajor`.
9
+ Підтримуються `||`-частини, hyphen-range (`7 - 9`), `^`, `~`, `>=`, `>`, `=`, bare-version;
10
+ `workspace:*` / `*` / `x` / `latest` — пропускаються (не блокують).
11
+
12
+ **Приклади допустимих діапазонів:**
13
+
14
+ ```
15
+ "@capacitor/core": "^8.0.0"
16
+ "@capacitor/core": ">=8"
17
+ "@capacitor/core": "8.x"
18
+ "@capacitor/core": "workspace:*"
19
+ ```
20
+
21
+ **Приклади неприйнятних діапазонів:**
22
+
23
+ ```
24
+ "@capacitor/core": "^7.0.0" // мажор 7 — занадто старий
25
+ "@capacitor/core": "*" // будь-яка — неприйнятна
26
+ "@capacitor/core": "latest" // неприйнятна
27
+ "@capacitor/core": ">=6" // нижня межа 6 — занадто старий
28
+ "@capacitor/core": "6 - 9" // hyphen-range: нижня межа 6 — неприйнятна
29
+ ```