@nitra/cursor 12.8.8 → 12.9.0

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 (132) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/bin/n-cursor.js +18 -10
  3. package/package.json +5 -5
  4. package/rules/abie/docs/index.md +0 -1
  5. package/rules/abie/lib/docs/http-route.md +11 -12
  6. package/rules/abie/lib/http-route.mjs +3 -0
  7. package/rules/abie/policy/health_check_policy/health_check_policy.mdc +3 -1
  8. package/rules/abie/policy/health_check_policy/health_check_policy.rego +27 -0
  9. package/rules/adr/docs/index.md +0 -1
  10. package/rules/adr/js/madr_format.mdc +13 -1
  11. package/rules/bun/docs/index.md +0 -1
  12. package/rules/bun/policy/package_json/package_json.rego +12 -0
  13. package/rules/capacitor/docs/index.md +0 -1
  14. package/rules/changelog/docs/index.md +0 -1
  15. package/rules/ci4/docs/index.md +0 -1
  16. package/rules/doc-files/docs/index.md +0 -1
  17. package/rules/doc-files/docs/main.md +7 -9
  18. package/rules/doc-files/main.mjs +2 -3
  19. package/rules/docker/docs/index.md +0 -1
  20. package/rules/efes/docs/index.md +0 -1
  21. package/rules/feedback/docs/index.md +0 -1
  22. package/rules/ga/docs/index.md +0 -1
  23. package/rules/graphql/docs/index.md +0 -1
  24. package/rules/hasura/docs/index.md +0 -1
  25. package/rules/hasura/js/docs/index.md +3 -2
  26. package/rules/hasura/js/docs/migrations.md +30 -0
  27. package/rules/hasura/js/migrations.mjs +47 -0
  28. package/rules/image-avif/docs/index.md +0 -1
  29. package/rules/image-compress/docs/index.md +0 -1
  30. package/rules/js/docs/index.md +0 -1
  31. package/rules/js/js/dep-policy.mjs +87 -0
  32. package/rules/js/js/docs/dep-policy.md +36 -0
  33. package/rules/js/js/docs/index.md +1 -0
  34. package/rules/js/policy/package_json/package_json.rego +16 -0
  35. package/rules/js-bun-db/docs/index.md +0 -1
  36. package/rules/js-bun-redis/docs/index.md +0 -1
  37. package/rules/js-mssql/docs/index.md +0 -1
  38. package/rules/js-run/docs/index.md +0 -1
  39. package/rules/k8s/docs/index.md +0 -1
  40. package/rules/nginx-default-tpl/docs/index.md +0 -1
  41. package/rules/npm-module/docs/index.md +0 -1
  42. package/rules/php/docs/index.md +0 -1
  43. package/rules/python/docs/index.md +0 -1
  44. package/rules/rego/docs/index.md +0 -1
  45. package/rules/rego/js/docs/index.md +3 -3
  46. package/rules/rego/js/docs/tooling.md +28 -0
  47. package/rules/rego/js/tooling.mjs +24 -0
  48. package/rules/rego/policy/package_json/package_json.rego +21 -0
  49. package/rules/rego/policy/package_json/target.json +4 -0
  50. package/rules/release/docs/index.md +0 -1
  51. package/rules/rust/docs/index.md +0 -1
  52. package/rules/rust/policy/lint_rust_yml/lint_rust_yml.rego +24 -0
  53. package/rules/rust/policy/package_json/package_json.rego +20 -0
  54. package/rules/rust/policy/package_json/target.json +4 -0
  55. package/rules/security/docs/index.md +0 -1
  56. package/rules/style/docs/index.md +0 -1
  57. package/rules/style/js/docs/index.md +2 -3
  58. package/rules/style/js/docs/tooling.md +14 -10
  59. package/rules/style/js/tooling.mjs +8 -2
  60. package/rules/style/policy/lint_style_yml/lint_style_yml.rego +5 -0
  61. package/rules/tauri/docs/index.md +0 -1
  62. package/rules/test/docs/index.md +0 -1
  63. package/rules/test/js/docs/index.md +2 -0
  64. package/rules/test/js/docs/no-console-store-restore.md +32 -0
  65. package/rules/test/js/docs/sandbox-aware-test.md +32 -0
  66. package/rules/test/js/no-console-store-restore.mjs +88 -0
  67. package/rules/test/js/sandbox-aware-test.mjs +89 -0
  68. package/rules/text/docs/index.md +0 -1
  69. package/rules/tool-surface/docs/index.md +0 -1
  70. package/rules/vue/docs/index.md +0 -1
  71. package/rules/worktree/docs/index.md +0 -1
  72. package/scripts/docs/hook.md +29 -0
  73. package/scripts/docs/index.md +1 -2
  74. package/scripts/hook.mjs +72 -0
  75. package/scripts/lib/docs/index.md +35 -36
  76. package/scripts/lib/docs/rule-meta.md +10 -9
  77. package/scripts/lib/docs/run-lint.md +9 -8
  78. package/scripts/lib/docs/run-rule.md +7 -7
  79. package/scripts/lib/fix/docs/index.md +0 -1
  80. package/scripts/lib/rule-meta.mjs +2 -1
  81. package/scripts/lib/run-lint.mjs +54 -3
  82. package/scripts/lib/run-rule.mjs +1 -2
  83. package/skills/adr-normalize/SKILL.md +1 -0
  84. package/skills/coverage-fix/SKILL.md +1 -0
  85. package/skills/doc-aggregate/SKILL.md +1 -0
  86. package/skills/doc-files/SKILL.md +1 -0
  87. package/skills/lint/SKILL.md +24 -19
  88. package/skills/llm-patch/SKILL.md +1 -0
  89. package/skills/publish-telegram/SKILL.md +1 -0
  90. package/skills/start-check/SKILL.md +1 -0
  91. package/skills/taze/SKILL.md +3 -2
  92. package/types/bin/n-cursor.d.ts +1 -1
  93. package/rules/abie/docs/fix.md +0 -37
  94. package/rules/adr/docs/fix.md +0 -37
  95. package/rules/bun/docs/fix.md +0 -30
  96. package/rules/capacitor/docs/fix.md +0 -36
  97. package/rules/changelog/docs/fix.md +0 -37
  98. package/rules/ci4/docs/fix.md +0 -32
  99. package/rules/doc-files/docs/fix.md +0 -29
  100. package/rules/docker/docs/fix.md +0 -35
  101. package/rules/efes/docs/fix.md +0 -37
  102. package/rules/feedback/docs/fix.md +0 -30
  103. package/rules/ga/docs/fix.md +0 -30
  104. package/rules/graphql/docs/fix.md +0 -37
  105. package/rules/hasura/docs/fix.md +0 -39
  106. package/rules/image-avif/docs/fix.md +0 -28
  107. package/rules/image-compress/docs/fix.md +0 -27
  108. package/rules/js/docs/fix.md +0 -37
  109. package/rules/js-bun-db/docs/fix.md +0 -30
  110. package/rules/js-bun-redis/docs/fix.md +0 -32
  111. package/rules/js-mssql/docs/fix.md +0 -30
  112. package/rules/js-run/docs/fix.md +0 -36
  113. package/rules/k8s/docs/fix.md +0 -31
  114. package/rules/nginx-default-tpl/docs/fix.md +0 -35
  115. package/rules/npm-module/docs/fix.md +0 -34
  116. package/rules/php/docs/fix.md +0 -35
  117. package/rules/python/docs/fix.md +0 -38
  118. package/rules/rego/docs/fix.md +0 -31
  119. package/rules/release/docs/fix.md +0 -28
  120. package/rules/rust/docs/fix.md +0 -32
  121. package/rules/security/docs/fix.md +0 -33
  122. package/rules/style/docs/fix.md +0 -28
  123. package/rules/tauri/docs/fix.md +0 -39
  124. package/rules/test/docs/fix.md +0 -31
  125. package/rules/text/docs/fix.md +0 -37
  126. package/rules/tool-surface/docs/fix.md +0 -32
  127. package/rules/vue/docs/fix.md +0 -32
  128. package/rules/worktree/docs/fix.md +0 -40
  129. package/scripts/docs/post-tool-use-fix.md +0 -32
  130. package/scripts/docs/worktree-cli.md +0 -27
  131. package/scripts/lib/docs/worktree.md +0 -42
  132. package/scripts/lib/fix/docs/run-fix-check.md +0 -33
package/CHANGELOG.md CHANGED
@@ -1,10 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.9.0] - 2026-06-24
4
+
5
+ ### Changed
6
+
7
+ - Додано підтримку hook-режиму для linting через `n-cursor
8
+ - Додано перевірку порту 8080 для HTTPRoute та політики HealthCheckPolicy
9
+
10
+ ## [12.8.9] - 2026-06-22
11
+
12
+ ### Changed
13
+
14
+ - ♻️ refactor(npm): Вдосконалено логіку `full`-scope правил у delta-режимі
15
+
3
16
  ## [12.8.8] - 2026-06-22
4
17
 
5
18
  ### Changed
6
19
 
7
- - ✨ feat(rules): policy mdc для всіх npm/rules/*/policy/<concern>/
20
+ - ✨ feat(rules): policy mdc для всіх npm/rules/\*/policy/<concern>/
8
21
 
9
22
  ## [12.8.7] - 2026-06-22
10
23
 
package/bin/n-cursor.js CHANGED
@@ -142,7 +142,7 @@ function sortConfigIdArrays(config) {
142
142
 
143
143
  /**
144
144
  * Імена правил з каталогу `rules/` поточної інсталяції пакету. Кожне правило — окремий
145
- * підкаталог `rules/<id>/`, у якому має бути `<id>.mdc`.
145
+ * підкаталог `rules/<id>/`, у якому має бути `main.mdc`.
146
146
  * @param {string} [bundledRulesDir] каталог `rules/` у корені пакету
147
147
  * @returns {Promise<string[]>} відсортовані id правил (імена підкаталогів)
148
148
  */
@@ -157,11 +157,11 @@ async function discoverBundledRuleNames(bundledRulesDir = BUNDLED_RULES_DIR) {
157
157
  const entries = await readdir(bundledRulesDir, { withFileTypes: true })
158
158
  const rules = entries
159
159
  .filter(e => e.isDirectory() && !e.name.startsWith('.'))
160
- .filter(e => existsSync(join(bundledRulesDir, e.name, `${e.name}.mdc`)))
160
+ .filter(e => existsSync(join(bundledRulesDir, e.name, 'main.mdc')))
161
161
  .map(e => e.name)
162
162
  .toSorted((a, b) => a.localeCompare(b))
163
163
  if (rules.length === 0) {
164
- throw new Error(`У каталозі rules/ пакету немає підкаталогів з <id>.mdc. Створіть ${CONFIG_FILE} вручну.`)
164
+ throw new Error(`У каталозі rules/ пакету немає підкаталогів з main.mdc. Створіть ${CONFIG_FILE} вручну.`)
165
165
  }
166
166
  return rules
167
167
  }
@@ -407,18 +407,18 @@ function normalizeRuleName(ruleName) {
407
407
  }
408
408
 
409
409
  /**
410
- * Читає вміст правила з каталогу `rules/<id>/<id>.mdc` установленого пакету
411
- * (наприклад `node_modules/@nitra/cursor/rules/<id>/<id>.mdc` або кеш npx).
410
+ * Читає вміст правила з каталогу `rules/<id>/main.mdc` установленого пакету
411
+ * (наприклад `node_modules/@nitra/cursor/rules/<id>/main.mdc` або кеш npx).
412
412
  * @param {string} rule елемент масиву rules з `.n-cursor.json`
413
413
  * @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
414
414
  * @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
415
415
  */
416
416
  async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
417
417
  const id = normalizeRuleName(rule)
418
- const bundledPath = join(bundledRulesDir, id, `${id}.mdc`)
418
+ const bundledPath = join(bundledRulesDir, id, 'main.mdc')
419
419
  if (!existsSync(bundledPath)) {
420
420
  throw new Error(
421
- `Немає файлу ${id}/${id}.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
421
+ `Немає файлу ${id}/main.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
422
422
  )
423
423
  }
424
424
  const text = await readFile(bundledPath, 'utf8')
@@ -1515,9 +1515,17 @@ try {
1515
1515
 
1516
1516
  break
1517
1517
  }
1518
+ case 'hook': {
1519
+ // Thin hook entrypoint для Claude Code hooks. Делегує в runLint, перекодовує exit 1→2.
1520
+ // --post-tool-use PostToolUse: file_path зі stdin JSON
1521
+ // --stop Stop: робоче дерево vs HEAD
1522
+ const { runHookCli } = await import('../scripts/hook.mjs')
1523
+ process.exitCode = await runHookCli(args)
1524
+
1525
+ break
1526
+ }
1518
1527
  case 'post-tool-use-check': {
1519
- // Викликається з .claude/settings.json як PostToolUse hook Claude Code.
1520
- // Маршрутизує змінений файл у релевантні правила і прокидає `fix` лише з ними.
1528
+ // @deprecated: використовуй `hook --post-tool-use`
1521
1529
  const code = await runPostToolUseCheckCli()
1522
1530
  process.exitCode = code
1523
1531
 
@@ -1661,7 +1669,7 @@ try {
1661
1669
  default: {
1662
1670
  console.error(`❌ Невідома команда: ${command}`)
1663
1671
  console.error(
1664
- ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-check, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), lint-doc-files, fix-doc-files, coverage, coverage-fix, analyze-escalation, taze, start-check, change, release, skill, trace, doc-aggregate`
1672
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), fix-doc-files, coverage, coverage-fix, analyze-escalation, taze, start-check, change, release, skill, trace, doc-aggregate`
1665
1673
  )
1666
1674
  process.exitCode = 1
1667
1675
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.8.8",
3
+ "version": "12.9.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -53,11 +53,11 @@
53
53
  "rename-yaml-extensions": "bun ./bin/n-cursor.js rename-yaml-extensions"
54
54
  },
55
55
  "dependencies": {
56
- "@7n/mt": "^0.5.0",
57
- "oxc-parser": "^0.128.0",
56
+ "@7n/mt": "^0.5.1",
57
+ "oxc-parser": "^0.137.0",
58
58
  "picomatch": "^4.0.4",
59
- "smol-toml": "^1.6.1",
60
- "yaml": "^2.8.3",
59
+ "smol-toml": "^1.7.0",
60
+ "yaml": "^2.9.0",
61
61
  "zod": "^4.4.3"
62
62
  },
63
63
  "engines": {
@@ -8,5 +8,4 @@ resource: npm/rules/abie/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -3,26 +3,25 @@ type: JS Module
3
3
  title: http-route.mjs
4
4
  resource: npm/rules/abie/lib/http-route.mjs
5
5
  docgen:
6
- crc: 3ec544d6
6
+ crc: 1ffd9c0b
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
7
9
  ---
8
10
 
9
- Файл надає інструмент для порівняльного аналізу конфігурації HTTP-маршрутів. Він виконує порівняння кількості `backendRefs` для сервісів `auth-run-hl` та `file-link-hl` у базових маніфестах пакета з кількістю патчів, визначеною в оверлеях. Цей механізм використовується для синхронізації кількості патчів у верхньому рівні з фактичною кількістю посилань у базі. (abie.mdc)
11
+ ## Огляд
10
12
 
11
- ## Поведінка
13
+ Виконує крос-документну аналітику для підрахунку `backendRefs` до спільних сервісів (`auth-run-hl`, `file-link-hl`) у base-маніфестах пакета, що знаходяться поза overlay `ua`. Використовується фіксований список спільних сервісів, визначений через `ABIE_SHARED_CROSS_NS_BACKEND_NAMES`. Функція `analyzeAbieSharedBackendRefsInPackageK8s` підраховує ці посилання. Це забезпечує синхронізацію числа патчів namespace в overlay із кількістю base-reference, використовуючи `ua_http_route-концерном` для забезпечення узгодженості (abie.mdc).
12
14
 
13
- ABIE_SHARED_CROSS_NS_BACKEND_NAMES
14
- Визначає список спільних сервісів, які підлягають аналітиці.
15
+ ## Поведінка
15
16
 
16
- analyzeAbieSharedBackendRefsInPackageK8s
17
- Збирає кількість посилань на спільні бекенди та порушення вимог до namespace у базових документах HTTPRoute пакета.
17
+ ABIE_SHARED_CROSS_NS_BACKEND_NAMES надає фіксований список назв спільних сервісів (`auth-run-hl`, `file-link-hl`), які підлягають аналізу.
18
+ analyzeAbieSharedBackendRefsInPackageK8s збирає кількість посилань на спільні сервіси (`backendRefs`) у base-маніфестах пакета (виключаючи overlay `ua`) та виявляє порушення вимог до цих посилань (наприклад, відсутність `namespace: dev` або `port: 8080` (abie.mdc)).
18
19
 
19
20
  ## Публічний API
20
21
 
21
- ABIE_SHARED_CROSS_NS_BACKEND_NAMES — Формує імена для крос-нішових зв'язків між бекендами. (abie.mdc)
22
-
23
- analyzeAbieSharedBackendRefsInPackageK8s — Збирає кількість спільних посилань `backendRefs` та базові помилки з YAML-файлів пакета, ігноруючи неймспейс `dev`. (abie.mdc)
22
+ ABIE_SHARED_CROSS_NS_BACKEND_NAMES — Визначає імена бекендів, що використовуються між різними неймспейсами.
23
+ analyzeAbieSharedBackendRefsInPackageK8s — Підраховує кількість посилань на спільні бекенди у YAML-файлах пакета (крім overlay ua) та фіксує базові помилки (без неймспейсу dev). (abie.mdc)
24
24
 
25
25
  ## Гарантії поведінки
26
26
 
27
- - Read-only: файл не виконує операцій запису у файлову систему.
28
- - Не звертається до мережі.
27
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -27,6 +27,9 @@ function checkSharedBackendRef(br, rel, errors) {
27
27
  if (typeof brRec.namespace !== 'string' || brRec.namespace !== 'dev') {
28
28
  errors.push(`${rel}: HTTPRoute backendRefs до ${name} має містити namespace: dev (abie.mdc)`)
29
29
  }
30
+ if (brRec.port !== 8080) {
31
+ errors.push(`${rel}: HTTPRoute backendRefs до ${name} має містити port: 8080 (abie.mdc)`)
32
+ }
30
33
  return 1
31
34
  }
32
35
 
@@ -8,10 +8,12 @@ Rego-пакет: `abie.health_check_policy`
8
8
 
9
9
  - `apiVersion` — точно `networking.gke.io/v1`;
10
10
  - `metadata.name` — непорожній рядок;
11
+ - `metadata.namespace` — `dev`;
11
12
  - `spec.default.config.type` — `HTTP`;
12
13
  - `spec.default.config.httpHealthCheck.requestPath` — непорожній, починається з `/`;
13
- - `spec.default.config.httpHealthCheck.port` — `8080`;
14
+ - `spec.default.config.httpHealthCheck.port` — `8080` (обов'язковий, відсутність теж помилка);
14
15
  - `spec.targetRef.kind` — `Service`;
16
+ - `spec.targetRef.group` — порожній рядок `''` (якщо поле присутнє);
15
17
  - `spec.targetRef.name` — `<metadata.name>-hl` (якщо `metadata.name` вже закінчується на `-hl` — використовується як є).
16
18
 
17
19
  FS-парність HCP↔Deployment та modeline `$schema` у `hc.yaml` — поза Rego, у `js/hc_pairing.mjs`. JS-опис структури та приклад — у `js/hc_pairing.mdc`.
@@ -61,6 +61,15 @@ deny contains "HealthCheckPolicy: metadata.name має бути непорожн
61
61
  trim_space(name) == ""
62
62
  }
63
63
 
64
+ # ── deny: metadata.namespace ──────────────────────────────────────────────
65
+
66
+ deny contains msg if {
67
+ is_health_check_policy
68
+ ns := object.get(object.get(input, "metadata", {}), "namespace", "")
69
+ ns != "dev"
70
+ msg := sprintf("HealthCheckPolicy: metadata.namespace має бути dev (зараз %q) (abie.mdc)", [ns])
71
+ }
72
+
64
73
  # ── deny: spec.default.config.type ────────────────────────────────────────
65
74
 
66
75
  deny contains "HealthCheckPolicy: spec.default.config.type має бути HTTP (abie.mdc)" if {
@@ -87,6 +96,12 @@ deny contains msg if {
87
96
 
88
97
  # ── deny: port == 8080 ────────────────────────────────────────────────────
89
98
 
99
+ deny contains "HealthCheckPolicy: spec.default.config.httpHealthCheck.port має бути 8080 (abie.mdc)" if {
100
+ is_health_check_policy
101
+ is_object(http_health_check)
102
+ object.get(http_health_check, "port", null) == null
103
+ }
104
+
90
105
  deny contains msg if {
91
106
  is_health_check_policy
92
107
  is_object(http_health_check)
@@ -108,6 +123,18 @@ deny contains msg if {
108
123
  msg := sprintf("HealthCheckPolicy: targetRef.kind має бути Service (зараз %q) (abie.mdc)", [kind])
109
124
  }
110
125
 
126
+ # ── deny: targetRef.group == '' ───────────────────────────────────────────
127
+
128
+ deny contains msg if {
129
+ is_health_check_policy
130
+ target_ref := object.get(object.get(input, "spec", {}), "targetRef", {})
131
+ is_object(target_ref)
132
+ group := object.get(target_ref, "group", null)
133
+ group != null
134
+ group != ""
135
+ msg := sprintf("HealthCheckPolicy: targetRef.group має бути порожнім рядком '' (зараз %q) (abie.mdc)", [group])
136
+ }
137
+
111
138
  # ── deny: targetRef.name = `<hcp.metadata.name>-hl` (exact, з нормалізацією)
112
139
 
113
140
  deny contains msg if {
@@ -8,5 +8,4 @@ resource: npm/rules/adr/
8
8
 
9
9
  | Файл | Тип |
10
10
  |---|---|
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -93,4 +93,16 @@ docs/adr/
93
93
  └── hooks.json # Cursor Agent stop-hooks для тих самих скриптів
94
94
  ```
95
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).
96
+ `.gitignore` у корені проєкту повинен містити базові рядки (`node_modules/`, `dist/`, `*.secret`) і патерни для ADR Stop-hook (**`.claude/hooks/*.log`**, `.claude/hooks/.normalize-state`, `.claude/hooks/.normalize.lock`). Канонічний фрагмент (дописується `npx @nitra/cursor`, коли правило `adr` увімкнене):
97
+
98
+ ```gitignore
99
+ node_modules/
100
+ dist/
101
+ *.secret
102
+
103
+ # @nitra/cursor (adr) — локальні артефакти Stop-hook, не коміти
104
+ .claude/hooks/*.log
105
+ .claude/hooks/.normalize-state
106
+ .claude/hooks/.normalize.lock
107
+ .claude/scheduled_tasks.lock
108
+ ```
@@ -8,5 +8,4 @@ resource: npm/rules/bun/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -24,6 +24,18 @@ deny contains msg if {
24
24
  msg := sprintf("package.json: поле %s — %s", [field, reason])
25
25
  }
26
26
 
27
+ # ── deny: scripts.lint / scripts.lint-* заборонені (bun.mdc lint) ────────
28
+
29
+ deny contains msg if {
30
+ is_object(input.scripts)
31
+ some script_name, _ in input.scripts
32
+ regex.match(`^lint(-.*)?$`, script_name)
33
+ msg := sprintf(
34
+ "package.json: scripts.%s заборонений — лінт запускається через n-cursor lint, не через package.json-скрипти (bun.mdc)",
35
+ [script_name]
36
+ )
37
+ }
38
+
27
39
  # ── deny: devDependencies — лише `@nitra/*` + root-only тестові peer/tools ─
28
40
 
29
41
  deny contains msg if {
@@ -8,5 +8,4 @@ resource: npm/rules/capacitor/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/changelog/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/ci4/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/doc-files/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -3,26 +3,24 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/doc-files/main.mjs
5
5
  docgen:
6
- crc: 64f215a5
6
+ crc: 82caefcd
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 90
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль перевіряє відповідність коду та документації, спираючись на конфігурацію `meta.json`. Функція `run` перевіряє консистентність між кодом та документацією. Функція `lint` перевіряє застарілі описи документації.
13
+ Модуль перевіряє відповідність конфігурацій політики джерелам коду, спираючись на `meta.json`. Він також аналізує документацію на відповідність джерелам коду, виявляючи застарілі або нерелевантні описи.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- run виконує перевірку консистентності між кодом та документацією.
18
-
19
- lint перевіряє, чи не застаріли описи документації, і може автоматично їх оновлювати, якщо це дозволено конфігурацією `meta.json`.
17
+ run виконує перевірку відповідності до політики, порівнюючи конфігурації з джерелами коду.
18
+ lint перевіряє, чи відповідає документація джерелам коду, виявляючи застарілі описи та сирітські доки, а також може автоматично їх виправляти за умови наявності конфігурації `meta.json: llmFix:true`.
20
19
 
21
20
  ## Публічний API
22
21
 
23
- run — виконує основну перевірку, яка включає аналіз JS-задач, застосування політик та посилання на MDC.
24
- lint — сканує файли документації на наявність застарілих елементів, з можливістю автоматичного виправлення за умови активації LLM.
25
- llmFix — виконує добровільну генерацію виправлень за допомогою LLM, якщо це дозволено в конфігурації.
22
+ run — основна точка входу, яка виконує перевірку (JS-залежності $\rightarrow$ політики $\rightarrow$ посилання MDC) та запускає перевірку на застарілі файли.
23
+ lint — збирає дані для перевірки документаційних файлів, з можливістю автоматичного виправлення за умови ввімкнення LLM-функціоналу.
26
24
 
27
25
  ## Гарантії поведінки
28
26
 
@@ -101,8 +101,7 @@ function collectStale(files, cwd) {
101
101
  */
102
102
  export async function lint(files, cwd = process.cwd(), { readOnly = false, llmFix = false } = {}) {
103
103
  const stale = collectStale(files, cwd)
104
- // Orphan-детект тільки при повному скані; при точковому (files визначено) — не релевантно
105
- const orphans = files === undefined ? scanOrphanedDocs(cwd) : []
104
+ const orphans = scanOrphanedDocs(cwd)
106
105
 
107
106
  if (stale.length === 0 && orphans.length === 0) return 0
108
107
  if (readOnly || !llmFix) {
@@ -132,7 +131,7 @@ export async function lint(files, cwd = process.cwd(), { readOnly = false, llmFi
132
131
  }
133
132
 
134
133
  const stillStale = collectStale(files, cwd)
135
- const stillOrphans = files === undefined ? scanOrphanedDocs(cwd) : []
134
+ const stillOrphans = scanOrphanedDocs(cwd)
136
135
  if (stillStale.length === 0 && stillOrphans.length === 0) return 0
137
136
  if (stillStale.length > 0) reportStale(stillStale)
138
137
  if (stillOrphans.length > 0) {
@@ -8,5 +8,4 @@ resource: npm/rules/docker/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/efes/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/feedback/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/ga/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/graphql/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/hasura/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -6,6 +6,7 @@ resource: npm/rules/hasura/js/
6
6
 
7
7
  # npm/rules/hasura/js
8
8
 
9
- | Файл | Тип |
10
- | ------------------------------------- | --------- |
9
+ | Файл | Тип |
10
+ |---|---|
11
11
  | [internal_urls.mjs](internal_urls.md) | JS Module |
12
+ | [migrations.mjs](migrations.md) | JS Module |
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: JS Module
3
+ title: migrations.mjs
4
+ resource: npm/rules/hasura/js/migrations.mjs
5
+ docgen:
6
+ crc: a8e2c4c4
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Функція перевіряє вміст директорії міграцій Hasura. Вона гарантує, що в директорії `hasura/migrations` відсутні файли з назвою `down.sql`, оскільки там мають бути лише файли `up.sql` (hasura.mdc).
14
+
15
+ ## Поведінка
16
+
17
+ 1. Перевіряє наявність директорії `hasura/migrations` відносно кореня репозиторію.
18
+ 2. Якщо директорія `hasura/migrations` відсутня, повідомляє, що перевірка `down.sql` не потрібна (hasura.mdc) і завершує роботу.
19
+ 3. Якщо директорія `hasura/migrations` існує, сканує її вміст.
20
+ 4. Збирає список усіх знайдених файлів з назвою `down.sql` у цій директорії.
21
+ 5. Якщо жоден файл `down.sql` не знайдено, повідомляє, що жоден `down.sql` не знайдено у `hasura/migrations/` (hasura.mdc) і завершує роботу.
22
+ 6. Якщо знайдено один або більше файлів `down.sql`, повідомляє про кожен знайдений файл, що `down.sql` заборонений у `hasura/migrations/` — у директорії міграції має бути лише `up.sql` (hasura.mdc) і завершує роботу.
23
+
24
+ ## Публічний API
25
+
26
+ check — перевіряє відсутність файлів `down.sql` у директорії міграцій, оскільки вони не використовуються.
27
+
28
+ ## Гарантії поведінки
29
+
30
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,47 @@
1
+ /** @see ./docs/migrations.md */
2
+ import { existsSync } from 'node:fs'
3
+ import { basename, join, relative } from 'node:path'
4
+
5
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
6
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
7
+
8
+ /** Відносний шлях до директорії міграцій від кореня проєкту. */
9
+ const MIGRATIONS_REL = 'hasura/migrations'
10
+
11
+ /**
12
+ * Перевіряє, що у `hasura/migrations/` відсутні файли `down.sql`.
13
+ * Директорія міграції має містити лише `up.sql` — `down.sql` у проєкті не використовується.
14
+ * @param {string} [cwdParam] корінь репозиторію
15
+ * @returns {Promise<number>} 0 — чисто, 1 — знайдено `down.sql`
16
+ */
17
+ export async function check(cwdParam = process.cwd()) {
18
+ const reporter = createCheckReporter()
19
+ const { pass, fail } = reporter
20
+
21
+ const cwd = cwdParam
22
+ const migrationsDir = join(cwd, MIGRATIONS_REL)
23
+
24
+ if (!existsSync(migrationsDir)) {
25
+ pass(`${MIGRATIONS_REL}/ відсутній — перевірка down.sql не потрібна (hasura.mdc)`)
26
+ return reporter.getExitCode()
27
+ }
28
+
29
+ /** @type {string[]} */
30
+ const offenders = []
31
+ await walkDir(migrationsDir, absPath => {
32
+ if (basename(absPath) === 'down.sql') {
33
+ offenders.push(relative(cwd, absPath))
34
+ }
35
+ })
36
+
37
+ if (offenders.length === 0) {
38
+ pass(`Жоден down.sql не знайдено у ${MIGRATIONS_REL}/ (hasura.mdc)`)
39
+ return reporter.getExitCode()
40
+ }
41
+
42
+ for (const file of offenders) {
43
+ fail(`${file}: down.sql заборонений у ${MIGRATIONS_REL}/ — у директорії міграції має бути лише up.sql (hasura.mdc)`)
44
+ }
45
+
46
+ return reporter.getExitCode()
47
+ }
@@ -8,5 +8,4 @@ resource: npm/rules/image-avif/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/image-compress/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/js/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,87 @@
1
+ /** @see ./docs/dep-policy.md */
2
+ import { readFile } from 'node:fs/promises'
3
+ import { join, relative, sep } from 'node:path'
4
+
5
+ import { parseSync } from 'oxc-parser'
6
+
7
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
8
+ import { loadCursorIgnorePaths } from '../../../scripts/lib/load-cursor-config.mjs'
9
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
10
+ import {
11
+ dynamicImportModule,
12
+ langFromPath,
13
+ requireCallModule,
14
+ walkAstWithAncestors,
15
+ } from '../../../scripts/utils/ast-scan-utils.mjs'
16
+
17
+ const JS_SOURCE_RE = /\.(?:[cm]?[jt]sx?)$/u
18
+
19
+ /** Пакети, заборонені як import-specifier у будь-якому JS/TS-файлі. */
20
+ const BANNED_SPECIFIERS = new Set(['@nitra/as-integrations-fastify'])
21
+
22
+ /**
23
+ * Витягає з джерела всі import-specifier'и (static + dynamic + require).
24
+ * @param {string} source текст файлу
25
+ * @param {string} filePath шлях до файлу (для вибору мови OXC-парсера)
26
+ * @returns {string[]} список specifier'ів
27
+ */
28
+ function extractImportSpecifiers(source, filePath) {
29
+ /** @type {string[]} */
30
+ const result = []
31
+ let parsed
32
+ try {
33
+ parsed = parseSync(filePath, source, { lang: langFromPath(filePath) })
34
+ } catch {
35
+ return result
36
+ }
37
+ for (const imp of parsed?.module?.staticImports ?? []) {
38
+ if (typeof imp?.moduleRequest?.value === 'string') result.push(imp.moduleRequest.value)
39
+ }
40
+ const program = parsed?.program
41
+ if (program && typeof program === 'object') {
42
+ walkAstWithAncestors(program, [], node => {
43
+ const dyn = dynamicImportModule(node)
44
+ if (dyn !== null) result.push(dyn)
45
+ const req = requireCallModule(node)
46
+ if (req !== null) result.push(req)
47
+ })
48
+ }
49
+ return result
50
+ }
51
+
52
+ /**
53
+ * Сканує всі JS/TS-файли проєкту на заборонені import-specifier'и (dep-policy.mdc).
54
+ * @param {string} [cwdParam] корінь репозиторію
55
+ * @returns {Promise<number>} 0 — чисто, 1 — знайдено заборонені specifier'и
56
+ */
57
+ export async function check(cwdParam = process.cwd()) {
58
+ const reporter = createCheckReporter()
59
+ const cwd = cwdParam
60
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
61
+
62
+ const files = []
63
+ await walkDir(cwd, p => {
64
+ if (JS_SOURCE_RE.test(p)) files.push(p)
65
+ }, ignorePaths)
66
+
67
+ let violations = 0
68
+ for (const absPath of files) {
69
+ const source = await readFile(absPath, 'utf8')
70
+ const specifiers = extractImportSpecifiers(source, absPath)
71
+ for (const spec of specifiers) {
72
+ if (BANNED_SPECIFIERS.has(spec)) {
73
+ const rel = relative(cwd, absPath)
74
+ reporter.fail(
75
+ `${rel}: заборонений import '${spec}' — використовуй @as-integrations/fastify (js.mdc dep-policy)`
76
+ )
77
+ violations += 1
78
+ }
79
+ }
80
+ }
81
+
82
+ if (violations === 0) {
83
+ reporter.pass(`dep-policy: перевірено ${files.length} файлів — заборонених import-specifier'ів немає (js.mdc)`)
84
+ }
85
+
86
+ return reporter.getExitCode()
87
+ }