@nitra/cursor 12.8.5 → 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 (202) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +5 -5
  3. package/package.json +1 -1
  4. package/rules/abie/js/http_route_base.mdc +25 -0
  5. package/rules/abie/js/ua_http_route.mdc +1 -1
  6. package/rules/abie/main.mdc +12 -0
  7. package/rules/adr/js/hooks.mdc +32 -0
  8. package/rules/adr/js/madr_format.mdc +96 -0
  9. package/rules/adr/js/settings_policy.mdc +34 -0
  10. package/rules/adr/main.mdc +13 -95
  11. package/rules/bun/js/bunfig.mdc +12 -0
  12. package/rules/bun/js/layout.mdc +60 -0
  13. package/rules/bun/js/lint.mdc +9 -0
  14. package/rules/bun/js/package_json.mdc +19 -0
  15. package/rules/bun/main.mdc +9 -61
  16. package/rules/capacitor/js/ios_spm.mdc +69 -0
  17. package/rules/capacitor/js/version.mdc +29 -0
  18. package/rules/capacitor/main.mdc +8 -22
  19. package/rules/changelog/js/agent-workflow.mdc +15 -0
  20. package/rules/changelog/js/changelog-format.mdc +33 -0
  21. package/rules/changelog/js/comparison-models.mdc +40 -0
  22. package/rules/changelog/main.mdc +4 -98
  23. package/rules/ci4/js/marksman_config.mdc +31 -0
  24. package/rules/ci4/js/vscode_extensions.mdc +33 -0
  25. package/rules/ci4/main.mdc +14 -14
  26. package/rules/docker/js/compile.mdc +44 -0
  27. package/rules/docker/js/hadolint.mdc +50 -0
  28. package/rules/docker/js/mirror.mdc +13 -0
  29. package/rules/docker/js/multistage.mdc +13 -0
  30. package/rules/docker/js/native-addon.mdc +43 -0
  31. package/rules/docker/js/nginx-tag.mdc +7 -0
  32. package/rules/docker/js/nginx-user.mdc +37 -0
  33. package/rules/docker/js/non-root.mdc +39 -0
  34. package/rules/docker/main.mdc +15 -196
  35. package/rules/ga/js/lint_toolchain.mdc +15 -0
  36. package/rules/ga/js/required_workflows.mdc +35 -0
  37. package/rules/ga/js/vscode.mdc +17 -0
  38. package/rules/ga/js/workflow_common.mdc +108 -0
  39. package/rules/ga/js/workflows.mdc +32 -0
  40. package/rules/ga/js/zizmor.mdc +7 -0
  41. package/rules/ga/main.mdc +17 -125
  42. package/rules/graphql/js/tooling.mdc +13 -0
  43. package/rules/graphql/js/vscode_extensions.mdc +13 -0
  44. package/rules/graphql/main.mdc +3 -22
  45. package/rules/hasura/js/internal_urls.mdc +27 -0
  46. package/rules/hasura/js/migrations.mdc +13 -0
  47. package/rules/hasura/js/svc_hl.mdc +17 -0
  48. package/rules/hasura/main.mdc +8 -30
  49. package/rules/image-avif/js/avif_generation.mdc +26 -0
  50. package/rules/image-avif/js/package_json_optout.mdc +21 -0
  51. package/rules/image-avif/main.mdc +7 -34
  52. package/rules/image-compress/js/package_json.mdc +7 -0
  53. package/rules/image-compress/js/package_setup.mdc +13 -0
  54. package/rules/image-compress/main.mdc +4 -12
  55. package/rules/js/docs/index.md +3 -3
  56. package/rules/js/js/dep-policy.mdc +17 -0
  57. package/rules/js/js/eslint-config.mdc +28 -0
  58. package/rules/js/js/extensions.mdc +8 -0
  59. package/rules/js/js/file-extensions.mdc +12 -0
  60. package/rules/js/js/for-in.mdc +26 -0
  61. package/rules/js/js/jscpd.mdc +42 -0
  62. package/rules/js/js/knip.mdc +15 -0
  63. package/rules/js/js/lint-js-workflow.mdc +58 -0
  64. package/rules/js/js/oxlintrc.mdc +20 -0
  65. package/rules/js/js/package-json.mdc +31 -0
  66. package/rules/js/js/tests.mdc +9 -0
  67. package/rules/js/js/utils-lib-structure.mdc +15 -0
  68. package/rules/js/main.mdc +21 -214
  69. package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
  70. package/rules/js-bun-db/js/connection.mdc +42 -0
  71. package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
  72. package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
  73. package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
  74. package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
  75. package/rules/js-bun-db/js/query-safety.mdc +117 -0
  76. package/rules/js-bun-db/js/sql-array.mdc +88 -0
  77. package/rules/js-bun-db/js/unsafe.mdc +65 -0
  78. package/rules/js-bun-db/main.mdc +15 -605
  79. package/rules/js-bun-redis/js/imports.mdc +47 -0
  80. package/rules/js-bun-redis/js/package_json.mdc +44 -0
  81. package/rules/js-bun-redis/main.mdc +3 -11
  82. package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
  83. package/rules/js-mssql/js/mssql-pool.mdc +56 -0
  84. package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
  85. package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
  86. package/rules/js-mssql/js/mssql-version.mdc +7 -0
  87. package/rules/js-mssql/main.mdc +10 -198
  88. package/rules/js-run/js/check-env.mdc +35 -0
  89. package/rules/js-run/js/conn-aliases.mdc +109 -0
  90. package/rules/js-run/js/jsconfig.mdc +20 -0
  91. package/rules/js-run/js/otel-configmap.mdc +6 -0
  92. package/rules/js-run/js/pino.mdc +6 -0
  93. package/rules/js-run/js/project-structure.mdc +11 -0
  94. package/rules/js-run/js/runtime.mdc +14 -0
  95. package/rules/js-run/js/scope.mdc +11 -0
  96. package/rules/js-run/js/settimeout.mdc +11 -0
  97. package/rules/js-run/js/temporal.mdc +5 -0
  98. package/rules/js-run/main.mdc +16 -218
  99. package/rules/k8s/js/configmap.mdc +41 -0
  100. package/rules/k8s/js/deployment_resources.mdc +49 -0
  101. package/rules/k8s/js/hasura_httproute.mdc +91 -0
  102. package/rules/k8s/js/hpa_apiversion.mdc +27 -0
  103. package/rules/k8s/js/ingress_gateway.mdc +16 -0
  104. package/rules/k8s/js/kustomize_structure.mdc +144 -0
  105. package/rules/k8s/js/lint_k8s.mdc +72 -0
  106. package/rules/k8s/js/multidoc_yaml.mdc +5 -0
  107. package/rules/k8s/js/network_policy.mdc +136 -0
  108. package/rules/k8s/js/schema_modeline.mdc +57 -0
  109. package/rules/k8s/js/service.mdc +44 -0
  110. package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
  111. package/rules/k8s/main.mdc +30 -843
  112. package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
  113. package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
  114. package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
  115. package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
  116. package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
  117. package/rules/nginx-default-tpl/main.mdc +6 -112
  118. package/rules/npm-module/js/docs/index.md +5 -5
  119. package/rules/npm-module/js/docs/rule_meta.md +6 -6
  120. package/rules/npm-module/js/docs/skill_meta.md +8 -8
  121. package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
  122. package/rules/npm-module/js/package_structure.mdc +62 -0
  123. package/rules/npm-module/js/rule_meta.mdc +11 -0
  124. package/rules/npm-module/js/skill_meta.mdc +11 -0
  125. package/rules/npm-module/main.mdc +10 -55
  126. package/rules/php/js/lint_php_yml.mdc +12 -0
  127. package/rules/php/js/tooling.mdc +66 -0
  128. package/rules/php/main.mdc +7 -66
  129. package/rules/python/js/lint_python_yml.mdc +23 -0
  130. package/rules/python/js/pyproject_toml.mdc +32 -0
  131. package/rules/python/js/tooling.mdc +23 -0
  132. package/rules/python/main.mdc +9 -33
  133. package/rules/rego/js/rego-lint.mdc +31 -0
  134. package/rules/rego/js/vscode_extensions.mdc +11 -0
  135. package/rules/rego/js/vscode_settings.mdc +13 -0
  136. package/rules/rego/main.mdc +8 -24
  137. package/rules/rust/js/coverage.mdc +28 -0
  138. package/rules/rust/js/lint.mdc +22 -0
  139. package/rules/rust/js/tauri_composition.mdc +8 -0
  140. package/rules/rust/js/vscode_extensions.mdc +12 -0
  141. package/rules/rust/main.mdc +8 -38
  142. package/rules/security/js/rego_policies.mdc +15 -0
  143. package/rules/security/js/sample_secret.mdc +19 -0
  144. package/rules/security/js/trufflehog.mdc +21 -0
  145. package/rules/security/main.mdc +7 -35
  146. package/rules/style/js/admin-table.mdc +88 -0
  147. package/rules/style/js/colors.mdc +21 -0
  148. package/rules/style/js/gap.mdc +22 -0
  149. package/rules/style/js/quasar-fixes.mdc +32 -0
  150. package/rules/style/js/quasar.mdc +7 -0
  151. package/rules/style/js/tooling.mdc +85 -0
  152. package/rules/style/main.mdc +13 -253
  153. package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
  154. package/rules/tauri/js/tool_surface.mdc +21 -0
  155. package/rules/tauri/js/tooling.mdc +25 -0
  156. package/rules/tauri/main.mdc +8 -78
  157. package/rules/test/js/cargo_mutants_config.mdc +18 -0
  158. package/rules/test/js/docs/index.md +7 -7
  159. package/rules/test/js/location.mdc +52 -0
  160. package/rules/test/js/no-console-store-restore.mdc +11 -0
  161. package/rules/test/js/no-process-chdir.mdc +15 -0
  162. package/rules/test/js/no-relative-fs-path.mdc +22 -0
  163. package/rules/test/js/sandbox-aware-test.mdc +28 -0
  164. package/rules/test/js/stryker_config.mdc +26 -0
  165. package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
  166. package/rules/test/main.mdc +18 -184
  167. package/rules/text/js/ci-lint-text.mdc +15 -0
  168. package/rules/text/js/cspell.mdc +81 -0
  169. package/rules/text/js/dotenv-linter.mdc +16 -0
  170. package/rules/text/js/forbidden-prettier.mdc +13 -0
  171. package/rules/text/js/markdownlint.mdc +25 -0
  172. package/rules/text/js/oxfmt.mdc +35 -0
  173. package/rules/text/js/package-json.mdc +26 -0
  174. package/rules/text/js/shellcheck.mdc +18 -0
  175. package/rules/text/js/v8r.mdc +23 -0
  176. package/rules/text/js/vscode.mdc +86 -0
  177. package/rules/text/main.mdc +20 -237
  178. package/rules/vue/js/composition-api.mdc +82 -0
  179. package/rules/vue/js/nheader-layout.mdc +171 -0
  180. package/rules/vue/js/node-imports.mdc +25 -0
  181. package/rules/vue/js/quasar-ui.mdc +32 -0
  182. package/rules/vue/js/structure.mdc +101 -0
  183. package/rules/vue/js/testing.mdc +32 -0
  184. package/rules/vue/js/tfm-translations.mdc +26 -0
  185. package/rules/vue/js/vite-config.mdc +126 -0
  186. package/rules/vue/js/vite-env.mdc +55 -0
  187. package/rules/vue/js/vue-imports.mdc +25 -0
  188. package/rules/vue/main.mdc +16 -640
  189. package/scripts/auto-rules.mjs +6 -6
  190. package/scripts/auto-skills.mjs +3 -3
  191. package/scripts/docs/auto-rules.md +17 -31
  192. package/scripts/docs/auto-skills.md +18 -163
  193. package/scripts/docs/index.md +16 -16
  194. package/scripts/lib/docs/index.md +36 -36
  195. package/scripts/lib/docs/mirror-parity.md +7 -7
  196. package/scripts/lib/docs/rule-meta.md +12 -12
  197. package/scripts/lib/docs/skill-meta.md +9 -9
  198. package/scripts/lib/docs/worktree-notice.md +10 -8
  199. package/scripts/lib/rule-meta.mjs +6 -6
  200. package/scripts/lib/skill-meta.mjs +6 -6
  201. package/scripts/lib/worktree-notice.mjs +2 -2
  202. package/scripts/utils/docs/index.md +14 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
9
+ ## [12.8.6] - 2026-06-22
10
+
11
+ ### Changed
12
+
13
+ - ✨ feat(rules/abie): http_route_base.mdc + активація + ua-домени у ua_http_route.mdc
14
+
3
15
  ## [12.8.5] - 2026-06-22
4
16
 
5
17
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -630,16 +630,16 @@ function buildClaudeLintParallelismSectionLines() {
630
630
  }
631
631
 
632
632
  /**
633
- * Рендерить секцію для CLAUDE.md: скіли з `meta.json.worktree === true` запускаються
633
+ * Рендерить секцію для CLAUDE.md: скіли з `main.json.worktree === true` запускаються
634
634
  * лише в окремому git-worktree (дублює fail-fast банер `SKILL.md` як завжди-читане правило).
635
635
  * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
636
636
  */
637
637
  function buildClaudeWorktreeEnforcementSectionLines() {
638
638
  return [
639
639
  '',
640
- '## Worktree-only skills (`meta.json` → `worktree: true`)',
640
+ '## Worktree-only skills (`main.json` → `worktree: true`)',
641
641
  '',
642
- 'Скіл із **`worktree: true`** у `meta.json` запускається **виключно** в окремому git-worktree (`.worktrees/<current-branch>-<suffix>/`) — **не** в основному дереві й **не** паралельно. Перший крок такого скіла (блок `n-cursor:worktree:start` у його `SKILL.md`) — **preflight**: якщо `git rev-parse --show-toplevel` не вказує під `.worktrees/`, **STOP** і не питай користувача про назву гілки; створи worktree від поточної гілки готовим snippet з `SKILL.md` за конвенцією `<current-branch>-<suffix>` і без shell expansion (без command substitution, variable expansion чи backticks). Чисте робоче дерево — **не** привід пропустити preflight.',
642
+ 'Скіл із **`worktree: true`** у `main.json` запускається **виключно** в окремому git-worktree (`.worktrees/<current-branch>-<suffix>/`) — **не** в основному дереві й **не** паралельно. Перший крок такого скіла (блок `n-cursor:worktree:start` у його `SKILL.md`) — **preflight**: якщо `git rev-parse --show-toplevel` не вказує під `.worktrees/`, **STOP** і не питай користувача про назву гілки; створи worktree від поточної гілки готовим snippet з `SKILL.md` за конвенцією `<current-branch>-<suffix>` і без shell expansion (без command substitution, variable expansion чи backticks). Чисте робоче дерево — **не** привід пропустити preflight.',
643
643
  ''
644
644
  ]
645
645
  }
@@ -795,10 +795,10 @@ async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
795
795
  const rootOnly = !worktree && meta?.requireRoot === true
796
796
  const entries = await readdir(srcDir, { withFileTypes: true })
797
797
  for (const entry of entries) {
798
- // Лише top-level файли скіла. `meta.json` — метадані (не для споживача);
798
+ // Лише top-level файли скіла. `main.json` — метадані (не для споживача);
799
799
  // підкаталоги (`js/` — скіл-специфічний код) виконуються з пакета через
800
800
  // `npx`, у проєкт не копіюються (як `npm/rules/<id>/js/`). Див. scripts.mdc.
801
- if (!entry.isFile() || entry.name === 'meta.json') continue
801
+ if (!entry.isFile() || entry.name === 'main.json') continue
802
802
  let content = await readFile(join(srcDir, entry.name), 'utf8')
803
803
  if (entry.name === 'SKILL.md') {
804
804
  content = injectWorktreeNotice(content, worktree)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.8.5",
3
+ "version": "12.8.7",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,25 @@
1
+ ## k8s: **HTTPRoute** у base-шарі — домени `aiml.live`
2
+
3
+ У **HTTPRoute** під шляхом **`…/k8s/.../base/...`** (сегмент `base` у path) усі значення **`spec.hostnames`** мають належати до домену **`aiml.live`**: точна відповідність **`aiml.live`**, wildcard **`*.aiml.live`** або будь-який піддомен (**`api.aiml.live`**, **`app.aiml.live`** тощо). Перевірка — case-insensitive.
4
+
5
+ Rego-пакет: **`abie.http_route_base`** (`policy/http_route_base/`). Цільові файли: `…/k8s/.../base/.../*.yaml` типу HTTPRoute.
6
+
7
+ ```yaml title="…/k8s/base/hr.yaml"
8
+ apiVersion: gateway.networking.k8s.io/v1
9
+ kind: HTTPRoute
10
+ metadata:
11
+ name: my-app
12
+ spec:
13
+ hostnames:
14
+ - "my-app.aiml.live" # ✓ піддомен aiml.live
15
+ parentRefs:
16
+ - name: gateway
17
+ ```
18
+
19
+ Недозволені в base-шарі (але коректні для ua-overlay — `abie.app`, `vybeerai.com.ua`):
20
+
21
+ ```yaml
22
+ hostnames:
23
+ - "abie.app" # ✗ ua-домен, не base
24
+ - "vybeerai.com.ua" # ✗ ua-домен, не base
25
+ ```
@@ -1,6 +1,6 @@
1
1
  ## k8s: overlay **HTTPRoute** (**ua**)
2
2
 
3
- За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`**, також дозволені префікси **`ua-*`**, наприклад **`ua-b2b`**). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
3
+ За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (хоча б один з доменів: **`abie.app`**, **`vybeerai.com.ua`**, **`*.abie.app`**, **`*.vybeerai.com.ua`**) та **`/spec/parentRefs/0/namespace`** (**`ua`**, також дозволені префікси **`ua-*`**, наприклад **`ua-b2b`**). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
4
4
 
5
5
  ### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`file-link-hl`**
6
6
 
@@ -6,8 +6,20 @@ version: '1.22'
6
6
 
7
7
  Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua**)), гілки **dev**, **ua** у **clean-merged-branch**, а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
8
8
 
9
+ ## Активація
10
+
11
+ Правило активується через **`.n-cursor.json`** у корені репозиторію:
12
+
13
+ ```json
14
+ { "rules": ["abie"] }
15
+ ```
16
+
17
+ Без цього запису `@nitra/cursor` пропускає всі abie-перевірки (JS-концерни та rego-полісі).
18
+
9
19
  [k8s-hc-yaml](./js/hc_pairing.mdc)
10
20
 
21
+ [k8s-http-route-base](./js/http_route_base.mdc)
22
+
11
23
  [k8s-http-route-ua](./js/ua_http_route.mdc)
12
24
 
13
25
  [k8s-nodeselector](./js/ua_node_selector.mdc)
@@ -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 |