@nitra/cursor 1.9.23 → 1.11.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 (84) hide show
  1. package/.claude-template/hooks/capture-decisions.sh +3 -3
  2. package/.claude-template/hooks/normalize-decisions.sh +370 -0
  3. package/CHANGELOG.md +52 -0
  4. package/bin/n-cursor.js +30 -29
  5. package/package.json +2 -1
  6. package/rules/abie/js/applies/check.mjs +24 -0
  7. package/rules/abie/js/env_dns/check.mjs +53 -0
  8. package/rules/abie/js/firebase_hosting/check.mjs +49 -0
  9. package/rules/abie/js/hc_pairing/check.mjs +58 -0
  10. package/rules/abie/js/ua_http_route/check.mjs +86 -0
  11. package/rules/abie/js/ua_node_selector/check.mjs +65 -0
  12. package/rules/abie/policy/base_deployment_preem/target.json +10 -0
  13. package/rules/abie/policy/clean_merged_ignore_branches/target.json +4 -0
  14. package/rules/abie/policy/health_check_policy/target.json +4 -0
  15. package/rules/abie/policy/http_route_base/target.json +4 -0
  16. package/rules/abie/utils/enabled.mjs +35 -0
  17. package/rules/abie/utils/env-dns.mjs +81 -0
  18. package/rules/abie/utils/hc-yaml.mjs +27 -0
  19. package/rules/abie/utils/http-route.mjs +93 -0
  20. package/rules/abie/utils/k8s-tree.mjs +102 -0
  21. package/rules/abie/utils/kustomization-patches.mjs +224 -0
  22. package/rules/abie/utils/overlay-paths.mjs +97 -0
  23. package/rules/abie/utils/yaml.mjs +72 -0
  24. package/rules/adr/adr.mdc +82 -18
  25. package/rules/adr/js/check.mjs +84 -40
  26. package/rules/adr/policy/settings_json/settings_json.rego +17 -11
  27. package/rules/adr/policy/settings_json/target.json +4 -0
  28. package/rules/adr/policy/settings_local_json/settings_local_json.rego +24 -12
  29. package/rules/adr/policy/settings_local_json/target.json +4 -0
  30. package/rules/bun/policy/bunfig/target.json +4 -0
  31. package/rules/bun/policy/package_json/target.json +4 -0
  32. package/rules/capacitor/policy/package_json/target.json +4 -0
  33. package/rules/docker/policy/lint_docker_yml/target.json +4 -0
  34. package/rules/docker/policy/package_json/target.json +4 -0
  35. package/rules/hasura/policy/svc_hl/target.json +4 -0
  36. package/rules/image-avif/policy/package_json/target.json +4 -0
  37. package/rules/image-compress/policy/package_json/target.json +4 -0
  38. package/rules/js-bun-db/policy/package_json/target.json +4 -0
  39. package/rules/js-bun-redis/policy/package_json/target.json +4 -0
  40. package/rules/js-lint/policy/lint_js_yml/target.json +4 -0
  41. package/rules/js-lint/policy/package_json/target.json +4 -0
  42. package/rules/js-mssql/policy/package_json/target.json +4 -0
  43. package/rules/js-run/policy/configmap/target.json +4 -0
  44. package/rules/js-run/policy/package_json/target.json +4 -0
  45. package/rules/k8s/policy/base_kustomization/target.json +4 -0
  46. package/rules/k8s/policy/base_manifest/target.json +10 -0
  47. package/rules/k8s/policy/gateway/target.json +4 -0
  48. package/rules/k8s/policy/hpa_pdb/target.json +4 -0
  49. package/rules/k8s/policy/kustomization/target.json +4 -0
  50. package/rules/k8s/policy/manifest/target.json +4 -0
  51. package/rules/k8s/policy/svc_hl_yaml/target.json +4 -0
  52. package/rules/k8s/policy/svc_yaml/target.json +4 -0
  53. package/rules/npm-module/policy/emit_types_config/target.json +4 -0
  54. package/rules/npm-module/policy/npm_package_json/target.json +4 -0
  55. package/rules/npm-module/policy/npm_publish_yml/target.json +4 -0
  56. package/rules/npm-module/policy/root_package_json/target.json +4 -0
  57. package/rules/php/policy/lint_php_yml/target.json +4 -0
  58. package/rules/php/policy/package_json/target.json +4 -0
  59. package/rules/rego/js/applies/check.mjs +54 -0
  60. package/rules/rego/policy/package_json/target.json +5 -0
  61. package/rules/rego/policy/vscode_extensions/target.json +5 -0
  62. package/rules/rego/policy/vscode_settings/target.json +5 -0
  63. package/rules/style-lint/policy/lint_style_yml/target.json +4 -0
  64. package/rules/style-lint/policy/package_json/target.json +4 -0
  65. package/rules/style-lint/policy/vscode_extensions/target.json +4 -0
  66. package/rules/style-lint/policy/vscode_settings/target.json +4 -0
  67. package/rules/text/policy/cspell/target.json +4 -0
  68. package/rules/text/policy/markdownlint/target.json +4 -0
  69. package/rules/text/policy/oxfmtrc/target.json +4 -0
  70. package/rules/text/policy/package_json/target.json +4 -0
  71. package/rules/text/policy/vscode_extensions/target.json +4 -0
  72. package/rules/text/policy/vscode_settings/target.json +4 -0
  73. package/rules/vue/policy/package_json/target.json +4 -0
  74. package/schemas/target.json +58 -0
  75. package/scripts/auto-skills.mjs +2 -0
  76. package/scripts/lint-conftest.mjs +65 -414
  77. package/scripts/sync-claude-config.mjs +70 -14
  78. package/scripts/utils/discover-checkable-rules.mjs +123 -0
  79. package/scripts/utils/resolve-target-files.mjs +109 -0
  80. package/scripts/utils/run-rule.mjs +131 -0
  81. package/skills/adr-normalize/SKILL.md +71 -0
  82. package/skills/adr-normalize/auto.md +1 -0
  83. package/rules/abie/js/check.mjs +0 -1152
  84. package/rules/rego/js/check.mjs +0 -106
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Перевіряє вимоги правила adr.mdc: ADR Stop-hook capture-decisions.sh у Claude Code.
2
+ * Перевіряє вимоги правила adr.mdc: ADR Stop-hook `capture-decisions.sh` і
3
+ * `normalize-decisions.sh` у Claude Code.
3
4
  *
4
5
  * Очікування:
5
- * - `.claude/hooks/capture-decisions.sh` існує і байт-у-байт збігається з канонічним
6
- * `.claude-template/hooks/capture-decisions.sh` пакета (sync керує файлом повністю).
7
- * - `.claude/settings.json` (project-shared) має managed-групу у `hooks.Stop`, яка
8
- * викликає цей bash-скрипт; маркер у `command` — `.claude/hooks/capture-decisions.sh`.
9
- * - `.claude/settings.local.json` (якщо існує) НЕ має дубля цієї managed-групи
10
- * після переходу на project-shared такий запис створив би два запуски на одну подію.
11
- * - `.gitignore` у корені містить шаблон, який покриває `.claude/hooks/capture-decisions.log`.
6
+ * - `.claude/hooks/capture-decisions.sh` та `.claude/hooks/normalize-decisions.sh`
7
+ * існують і байт-у-байт збігаються з канонічними `.claude-template/hooks/*`
8
+ * пакета (sync керує файлами повністю).
9
+ * - `.claude/settings.json` (project-shared) має managed-групи у `hooks.Stop` для
10
+ * обох скриптів (маркери у `command` самі шляхи до скриптів).
11
+ * - `.claude/settings.local.json` (якщо існує) НЕ має дублів цих managed-груп
12
+ * після переходу на project-shared такі записи створили б два запуски на одну подію.
13
+ * - `.gitignore` у корені містить шаблон, який покриває
14
+ * `.claude/hooks/capture-decisions.log` і `.claude/hooks/normalize-decisions.log`.
12
15
  *
13
16
  * LLM CLI (`claude` або `cursor-agent`) у `PATH` — інформативна перевірка: якщо жодного
14
17
  * немає, скрипт працює, але мовчки виходить, тому це warning, а не fail.
@@ -21,26 +24,48 @@ import { fileURLToPath } from 'node:url'
21
24
 
22
25
  import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
23
26
 
24
- const PROJECT_HOOK_PATH = '.claude/hooks/capture-decisions.sh'
27
+ /** Один hook-артефакт: bash-скрипт + його лог-файл, які перевіряємо однотипно. */
28
+ const HOOK_ARTIFACTS = /** @type {const} */ ([
29
+ { scriptName: 'capture-decisions.sh', logName: 'capture-decisions.log' },
30
+ { scriptName: 'normalize-decisions.sh', logName: 'normalize-decisions.log' }
31
+ ])
32
+
25
33
  const PROJECT_SETTINGS_PATH = '.claude/settings.json'
26
- const PROJECT_LOG_PATH = '.claude/hooks/capture-decisions.log'
27
34
  const EOL_RE = /\r?\n/u
28
35
 
29
36
  const here = dirname(fileURLToPath(import.meta.url))
30
- /** Канонічний bundled-скрипт у пакеті джерело правди для звірки з проєктним. */
31
- const BUNDLED_HOOK_PATH = join(here, '..', '.claude-template', 'hooks', 'capture-decisions.sh')
37
+ const BUNDLED_HOOKS_DIR = join(here, '..', '..', '..', '.claude-template', 'hooks')
38
+
39
+ /**
40
+ * Відносний шлях до managed hook-скрипта у проєкті.
41
+ * @param {string} scriptName базове ім'я скрипта (наприклад `capture-decisions.sh`)
42
+ * @returns {string} `.claude/hooks/<scriptName>`
43
+ */
44
+ function projectHookPath(scriptName) {
45
+ return `.claude/hooks/${scriptName}`
46
+ }
47
+
48
+ /**
49
+ * Відносний шлях до лог-файлу managed hook'а у проєкті.
50
+ * @param {string} logName базове ім'я лог-файлу (наприклад `capture-decisions.log`)
51
+ * @returns {string} `.claude/hooks/<logName>`
52
+ */
53
+ function projectLogPath(logName) {
54
+ return `.claude/hooks/${logName}`
55
+ }
32
56
 
33
57
  /**
34
- * Чи містить рядок `.gitignore` шаблон, який покриває `.claude/hooks/capture-decisions.log`.
58
+ * Чи містить рядок `.gitignore` шаблон, який покриває цей конкретний лог-файл хука.
35
59
  * Враховує точний шлях, glob `.claude/hooks/*.log` та широкий glob `**\/*.log`.
36
60
  * @param {string} line одна нормалізована (trim) лінія `.gitignore`
37
- * @returns {boolean} `true`, якщо лінія матчить лог-файл хука
61
+ * @param {string} logPath шлях `.claude/hooks/<name>.log`, який треба покрити
62
+ * @returns {boolean} `true`, якщо лінія матчить цей лог-файл
38
63
  */
39
- function gitignoreLineCoversHookLog(line) {
64
+ function gitignoreLineCoversHookLog(line, logPath) {
40
65
  if (!line || line.startsWith('#')) {
41
66
  return false
42
67
  }
43
- if (line === PROJECT_LOG_PATH) {
68
+ if (line === logPath) {
44
69
  return true
45
70
  }
46
71
  if (line === '.claude/hooks/*.log' || line === '.claude/hooks/**/*.log') {
@@ -53,28 +78,28 @@ function gitignoreLineCoversHookLog(line) {
53
78
  }
54
79
 
55
80
  /**
56
- * Перевіряє наявність і канонічність `.claude/hooks/capture-decisions.sh` у проєкті.
81
+ * Перевіряє наявність і канонічність одного hook-скрипта.
57
82
  * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
83
+ * @param {string} scriptName базове ім'я скрипта (наприклад `capture-decisions.sh`)
58
84
  * @returns {Promise<void>}
59
85
  */
60
- async function checkHookScript(reporter) {
86
+ async function checkHookScript(reporter, scriptName) {
61
87
  const { pass, fail } = reporter
62
- if (!existsSync(PROJECT_HOOK_PATH)) {
63
- fail(`${PROJECT_HOOK_PATH} не існує — запусти \`npx @nitra/cursor\` (правило adr копіює канонічний скрипт)`)
88
+ const projectPath = projectHookPath(scriptName)
89
+ const bundledPath = join(BUNDLED_HOOKS_DIR, scriptName)
90
+ if (!existsSync(projectPath)) {
91
+ fail(`${projectPath} не існує — запусти \`npx @nitra/cursor\` (правило adr копіює канонічний скрипт)`)
64
92
  return
65
93
  }
66
- if (!existsSync(BUNDLED_HOOK_PATH)) {
67
- fail(`канонічний скрипт у пакеті не знайдено: ${BUNDLED_HOOK_PATH} — перевстанови @nitra/cursor`)
94
+ if (!existsSync(bundledPath)) {
95
+ fail(`канонічний скрипт у пакеті не знайдено: ${bundledPath} — перевстанови @nitra/cursor`)
68
96
  return
69
97
  }
70
- const [project, bundled] = await Promise.all([
71
- readFile(PROJECT_HOOK_PATH, 'utf8'),
72
- readFile(BUNDLED_HOOK_PATH, 'utf8')
73
- ])
98
+ const [project, bundled] = await Promise.all([readFile(projectPath, 'utf8'), readFile(bundledPath, 'utf8')])
74
99
  if (project === bundled) {
75
- pass(`${PROJECT_HOOK_PATH} збігається з канонічним`)
100
+ pass(`${projectPath} збігається з канонічним`)
76
101
  } else {
77
- fail(`${PROJECT_HOOK_PATH} відрізняється від канонічного — запусти \`npx @nitra/cursor\` для повторного синку`)
102
+ fail(`${projectPath} відрізняється від канонічного — запусти \`npx @nitra/cursor\` для повторного синку`)
78
103
  }
79
104
  }
80
105
 
@@ -95,25 +120,42 @@ function checkProjectSettings(reporter) {
95
120
  }
96
121
 
97
122
  /**
98
- * Перевіряє `.gitignore` на ігнорування лог-файлу хука.
123
+ * Перевіряє `.gitignore` на ігнорування лог-файлу одного хука.
124
+ * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
125
+ * @param {string} logName базове ім'я лог-файлу (наприклад `capture-decisions.log`)
126
+ * @param {string} gitignoreContent попередньо прочитаний вміст `.gitignore`
127
+ * @returns {void}
128
+ */
129
+ function checkGitignoreForLog(reporter, logName, gitignoreContent) {
130
+ const { pass, fail } = reporter
131
+ const logPath = projectLogPath(logName)
132
+ const covers = gitignoreContent
133
+ .split(EOL_RE)
134
+ .map(l => l.trim())
135
+ .some(line => gitignoreLineCoversHookLog(line, logPath))
136
+ if (covers) {
137
+ pass(`.gitignore покриває ${logPath}`)
138
+ } else {
139
+ fail(`.gitignore не ігнорує \`${logPath}\` — додай цей рядок`)
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Перевіряє `.gitignore` для всіх hook-логів одним проходом.
99
145
  * @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
100
146
  * @returns {Promise<void>}
101
147
  */
102
148
  async function checkGitignore(reporter) {
103
- const { pass, fail } = reporter
149
+ const { fail } = reporter
104
150
  if (!existsSync('.gitignore')) {
105
- fail(`.gitignore не існує додай рядок \`${PROJECT_LOG_PATH}\``)
151
+ for (const { logName } of HOOK_ARTIFACTS) {
152
+ fail(`.gitignore не існує — додай рядок \`${projectLogPath(logName)}\``)
153
+ }
106
154
  return
107
155
  }
108
156
  const content = await readFile('.gitignore', 'utf8')
109
- const covers = content
110
- .split(EOL_RE)
111
- .map(l => l.trim())
112
- .some(line => gitignoreLineCoversHookLog(line))
113
- if (covers) {
114
- pass(`.gitignore покриває ${PROJECT_LOG_PATH}`)
115
- } else {
116
- fail(`.gitignore не ігнорує \`${PROJECT_LOG_PATH}\` — додай цей рядок`)
157
+ for (const { logName } of HOOK_ARTIFACTS) {
158
+ checkGitignoreForLog(reporter, logName, content)
117
159
  }
118
160
  }
119
161
 
@@ -166,7 +208,9 @@ function checkLlmCliAvailable(reporter) {
166
208
  */
167
209
  export async function check() {
168
210
  const reporter = createCheckReporter()
169
- await checkHookScript(reporter)
211
+ for (const { scriptName } of HOOK_ARTIFACTS) {
212
+ await checkHookScript(reporter, scriptName)
213
+ }
170
214
  checkProjectSettings(reporter)
171
215
  await checkGitignore(reporter)
172
216
  checkLlmCliAvailable(reporter)
@@ -1,13 +1,13 @@
1
- # Порт перевірки `.claude/settings.json` з `npm/scripts/check-adr.mjs` (adr.mdc):
2
- # `hooks.Stop[*]` має містити групу, де хоча б один елемент `hooks[]` має `command`
3
- # зі substring `.claude/hooks/capture-decisions.sh`.
1
+ # Перевірка `.claude/settings.json` для правила adr.mdc:
2
+ # `hooks.Stop[*]` має містити дві managed-групи capture і normalize у
3
+ # кожній хоча б один елемент `hooks[]` з відповідним маркером у `command`.
4
4
  #
5
5
  # Запуск (локально):
6
- # conftest test .claude/settings.json -p npm/policy/adr \
6
+ # conftest test .claude/settings.json -p npm/rules/adr/policy \
7
7
  # --namespace adr.settings_json
8
8
  #
9
- # Hash-порівняння bash-скрипта з канонічним bundled-варіантом і `.gitignore`-перевірки
10
- # — у JS (`check-adr.mjs`).
9
+ # Hash-порівняння bash-скриптів з канонічними bundled-варіантами і `.gitignore`-перевірки
10
+ # — у JS (`js/check.mjs`).
11
11
  #
12
12
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
13
13
  # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
@@ -16,16 +16,22 @@ package adr.settings_json
16
16
 
17
17
  import rego.v1
18
18
 
19
- hook_command_marker := ".claude/hooks/capture-decisions.sh"
19
+ capture_marker := ".claude/hooks/capture-decisions.sh"
20
+ normalize_marker := ".claude/hooks/normalize-decisions.sh"
20
21
 
21
22
  deny contains msg if {
22
- not has_adr_stop_hook
23
+ not has_stop_hook_with_marker(capture_marker)
23
24
  msg := ".claude/settings.json: відсутній Stop-hook для `capture-decisions.sh` у hooks.Stop (adr.mdc)"
24
25
  }
25
26
 
26
- # Чи є в `hooks.Stop[*].hooks[*].command` рядок з маркером скрипта.
27
- has_adr_stop_hook if {
27
+ deny contains msg if {
28
+ not has_stop_hook_with_marker(normalize_marker)
29
+ msg := ".claude/settings.json: відсутній Stop-hook для `normalize-decisions.sh` у hooks.Stop (adr.mdc)"
30
+ }
31
+
32
+ # Чи є в `hooks.Stop[*].hooks[*].command` рядок з заданим маркером.
33
+ has_stop_hook_with_marker(marker) if {
28
34
  some group in object.get(object.get(input, "hooks", {}), "Stop", [])
29
35
  some hook in object.get(group, "hooks", [])
30
- contains(object.get(hook, "command", ""), hook_command_marker)
36
+ contains(object.get(hook, "command", ""), marker)
31
37
  }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".claude/settings.json" }
4
+ }
@@ -1,10 +1,10 @@
1
- # Порт перевірки `.claude/settings.local.json` з `npm/scripts/check-adr.mjs`
2
- # (adr.mdc): після переходу на project-shared `settings.json` цей файл (якщо є)
3
- # НЕ повинен мати дубля Stop-хука з маркером `.claude/hooks/capture-decisions.sh`,
4
- # інакше один і той самий скрипт виконається двічі на одну подію.
1
+ # Перевірка `.claude/settings.local.json` для правила adr.mdc: після переходу на
2
+ # project-shared `settings.json` цей файл (якщо є) НЕ повинен мати дубля жодного
3
+ # з керованих Stop-хуків (`capture-decisions.sh` або `normalize-decisions.sh`),
4
+ # інакше відповідний скрипт виконається двічі на одну подію.
5
5
  #
6
6
  # Запуск (локально):
7
- # conftest test .claude/settings.local.json -p npm/policy/adr \
7
+ # conftest test .claude/settings.local.json -p npm/rules/adr/policy \
8
8
  # --namespace adr.settings_local_json
9
9
  #
10
10
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
@@ -14,15 +14,27 @@ package adr.settings_local_json
14
14
 
15
15
  import rego.v1
16
16
 
17
- hook_command_marker := ".claude/hooks/capture-decisions.sh"
17
+ capture_marker := ".claude/hooks/capture-decisions.sh"
18
+ normalize_marker := ".claude/hooks/normalize-decisions.sh"
18
19
 
19
- duplicate_template := concat(" ", [
20
- ".claude/settings.local.json: видали дубль Stop-хука для",
21
- "`capture-decisions.sh` він уже у project-shared settings.json (adr.mdc)",
22
- ])
20
+ deny contains msg if {
21
+ has_stop_hook_with_marker(capture_marker)
22
+ msg := concat(" ", [
23
+ ".claude/settings.local.json: видали дубль Stop-хука для",
24
+ "`capture-decisions.sh` — він уже у project-shared settings.json (adr.mdc)",
25
+ ])
26
+ }
27
+
28
+ deny contains msg if {
29
+ has_stop_hook_with_marker(normalize_marker)
30
+ msg := concat(" ", [
31
+ ".claude/settings.local.json: видали дубль Stop-хука для",
32
+ "`normalize-decisions.sh` — він уже у project-shared settings.json (adr.mdc)",
33
+ ])
34
+ }
23
35
 
24
- deny contains duplicate_template if {
36
+ has_stop_hook_with_marker(marker) if {
25
37
  some group in object.get(object.get(input, "hooks", {}), "Stop", [])
26
38
  some hook in object.get(group, "hooks", [])
27
- contains(object.get(hook, "command", ""), hook_command_marker)
39
+ contains(object.get(hook, "command", ""), marker)
28
40
  }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".claude/settings.local.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "bunfig.toml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-docker.yml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "hasura/k8s/base/svc-hl.yaml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-js.yml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/k8s/*/configmap.yaml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/k8s/**/base/**/kustomization.yaml" }
4
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "walkGlob": [
5
+ "**/k8s/**/base/**/*.yaml",
6
+ "**/k8s/**/base/**/*.yml",
7
+ "!**/k8s/**/base/**/kustomization.yaml"
8
+ ]
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/k8s/**/kustomization.yaml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/k8s/**/svc-hl.yaml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "walkGlob": "**/k8s/**/svc.yaml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "npm/tsconfig.emit-types.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "npm/package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/npm-publish.yml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-php.yml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Applies-гейт правила `rego` (rego.mdc): правило застосовне, лише якщо в репозиторії є
3
+ * хоча б один `.rego`-файл (під типовими skip-ами і `.n-cursor.json:ignore`).
4
+ *
5
+ * Якщо `.rego` нема — CLI пропускає правило цілком (включно з polices `package_json`,
6
+ * `vscode_extensions`, `vscode_settings`), бо вимоги rego-tooling неактуальні. Якщо є — CLI
7
+ * прогонить policy-концерни через `target.json`-маніфести у `rules/rego/policy/<name>/`.
8
+ *
9
+ * JS тут лишається лише як cross-file гейт: walkDir не виразити декларативно через `target.json`.
10
+ * Друк короткого pass-повідомлення з контекстом робить `check()` (необовʼязковий).
11
+ */
12
+ import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
13
+ import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
14
+ import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
15
+
16
+ /**
17
+ * Чи є хоча б один `.rego`-файл у дереві від `cwd`. Зупиняється на першому матчі.
18
+ * @param {string} root абсолютний шлях кореня
19
+ * @param {string[]} ignorePaths шляхи каталогів, повністю виключених з обходу
20
+ * @returns {Promise<boolean>} `true`, якщо знайдено хоч один `.rego`
21
+ */
22
+ async function projectHasRegoFiles(root, ignorePaths) {
23
+ let found = false
24
+ await walkDir(
25
+ root,
26
+ p => {
27
+ if (p.endsWith('.rego')) {
28
+ found = true
29
+ }
30
+ },
31
+ ignorePaths
32
+ )
33
+ return found
34
+ }
35
+
36
+ /**
37
+ * Rule-level applies-гейт: CLI пропускає правило, якщо в репо немає `.rego` файлів.
38
+ * @returns {Promise<boolean>} `true`, якщо правило застосовне
39
+ */
40
+ export async function applies() {
41
+ const root = process.cwd()
42
+ const ignorePaths = await loadCursorIgnorePaths(root)
43
+ return projectHasRegoFiles(root, ignorePaths)
44
+ }
45
+
46
+ /**
47
+ * Друкує короткий context-pass — самі полісі прогонить CLI через `policy/<name>/target.json`.
48
+ * @returns {Promise<number>} 0 — все ок (фактичні порушення повертають policy-концерни)
49
+ */
50
+ export async function check() {
51
+ const reporter = createCheckReporter()
52
+ reporter.pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')
53
+ return reporter.getExitCode()
54
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json", "required": true },
4
+ "missingMessage": "package.json не існує — створи згідно rego.mdc (rego.package_json)"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/extensions.json", "required": true },
4
+ "missingMessage": ".vscode/extensions.json не існує — створи згідно rego.mdc (rego.vscode_extensions)"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/settings.json", "required": true },
4
+ "missingMessage": ".vscode/settings.json не існує — створи згідно rego.mdc (rego.vscode_settings)"
5
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-style.yml" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/extensions.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/settings.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".cspell.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".markdownlint-cli2.jsonc" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".oxfmtrc.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": "package.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/extensions.json" }
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/settings.json" }
4
+ }