@nitra/cursor 1.13.2 → 1.13.11

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 (49) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/bin/n-cursor.js +4 -2
  3. package/package.json +4 -2
  4. package/rules/ga/fix/workflows/check.mjs +6 -109
  5. package/rules/ga/ga.mdc +10 -15
  6. package/rules/ga/policy/package_json/package_json.rego +20 -0
  7. package/rules/ga/policy/package_json/target.json +8 -0
  8. package/rules/ga/policy/package_json/template/package.json.contains.json +1 -0
  9. package/rules/ga/policy/vscode_extensions/target.json +8 -0
  10. package/rules/ga/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  11. package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +18 -0
  12. package/rules/ga/policy/vscode_settings/target.json +8 -0
  13. package/rules/ga/policy/vscode_settings/template/settings.json.snippet.json +1 -0
  14. package/rules/ga/policy/vscode_settings/vscode_settings.rego +22 -0
  15. package/rules/ga/policy/zizmor_yml/target.json +8 -0
  16. package/rules/ga/policy/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
  17. package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +27 -0
  18. package/rules/js-lint/fix/tooling/check.mjs +6 -83
  19. package/rules/js-lint/policy/jscpd/jscpd.rego +38 -0
  20. package/rules/js-lint/policy/jscpd/target.json +8 -0
  21. package/rules/js-lint/policy/vscode_extensions/target.json +8 -0
  22. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +25 -0
  23. package/rules/rego/lint/lint.mjs +5 -4
  24. package/rules/rego/policy/package_json/package_json.rego +8 -29
  25. package/rules/rego/policy/package_json/template/package.json.snippet.json +1 -0
  26. package/rules/rego/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  27. package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +7 -11
  28. package/rules/rego/policy/vscode_settings/template/settings.json.snippet.json +6 -0
  29. package/rules/rego/policy/vscode_settings/vscode_settings.rego +19 -27
  30. package/rules/rego/rego.mdc +10 -8
  31. package/rules/security/fix/gitleaks/check.mjs +8 -45
  32. package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +12 -0
  33. package/rules/security/policy/gitleaks/gitleaks.rego +17 -0
  34. package/rules/security/policy/gitleaks/target.json +8 -0
  35. package/rules/security/policy/package_json/package_json.rego +22 -59
  36. package/rules/security/policy/package_json/template/package.json.contains.json +1 -0
  37. package/rules/security/policy/package_json/template/package.json.deny.json +4 -0
  38. package/rules/security/policy/package_json/template/package.json.snippet.json +1 -0
  39. package/rules/security/security.mdc +7 -26
  40. package/rules/security/todo.MD +27 -0
  41. package/rules/vue/fix/packages/check.mjs +7 -64
  42. package/rules/vue/policy/package_json/package_json.rego +45 -2
  43. package/rules/vue/vue.mdc +15 -2
  44. package/scripts/ensure-nitra-cursor-dev-dependencies.mjs +41 -21
  45. package/scripts/utils/check-mdc-template-refs.mjs +47 -0
  46. package/scripts/utils/inline-template-links.mjs +60 -0
  47. package/scripts/utils/run-conftest-batch.mjs +60 -33
  48. package/scripts/utils/run-rule.mjs +16 -1
  49. package/scripts/utils/template.mjs +215 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,104 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.11] - 2026-05-17
8
+
9
+ ### Added
10
+
11
+ - `rego` rule template/ міграція (Phase 3): 3 концерни — `package_json` (snippet із збереженням `trim_space` tolerance), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object 2-level + окремий deny на non-object block).
12
+ - Drift-тести у кожному `*_test.rego`.
13
+
14
+ ### Changed
15
+
16
+ - `rego.package_json.rego` — замість двох inline-deny (missing + wrong-value через `regex/trim_space`) тепер один snippet-walker через `data.template.snippet`.
17
+ - `rego.vscode_extensions.rego` — замість inline `"tsandall.opa"` тепер subset-of через `data.template.snippet.recommendations`.
18
+ - `rego.vscode_settings.rego` — 2-рівневий snippet-walker з гардом `is_object(inner)` для випадку, коли block існує, але не обʼєкт.
19
+ - `rego.mdc` — inline `package.json` snippet замінено на template-link; додано посилання на `.vscode/{extensions,settings}.json` template-файли. Виправлено застаріле `Цілі — npm/policy/` → `npm/rules/`.
20
+ - `docs/adr/template-dir-concern-inventory.md` — позначено 3 `rego.*` концерни як ✓; додано Phase 3 у прогрес-секцію.
21
+
22
+ ## [1.13.10] - 2026-05-17
23
+
24
+ ### Fixed
25
+
26
+ - `runLintRego` (`npm/rules/rego/lint/lint.mjs`) — `LINT_TARGETS` вказував на застарілий шлях `npm/policy` (не існує після Phase 1 реструктуризації), тож `bun run lint-rego` мовчки exit 0 без реальної перевірки. Тепер `LINT_TARGETS = ['npm/rules']` — `opa check --strict`, `regal lint`, `conftest verify` реально проходять по всіх 111 `.rego`-файлах. TDD-регресія у `lint.test.mjs` (broken-syntax + well-formed fixtures).
27
+
28
+ ### Changed
29
+
30
+ - `.regal/config.yaml` — додано `idiomatic.directory-package-mismatch` і `imports.unresolved-reference` у `ignore` (інтенціональні конвенції проєкту: package = `<rule>.<concern>` у `<rule>/policy/<concern>/`; `data.template.*` ін'єктиться runtime через `--data`). `style.line-length.max-line-length: 220` — узгоджено з `opa fmt` (тримає малі обʼєкти single-line).
31
+ - `*_test.rego` з порушенням `test-outside-test-package` (4 файли: `js-lint.jscpd`, `js-lint.vscode_extensions`, `security.gitleaks`, `vue.package_json`) — перейменовано в `<package>_test` із явним `import data.<package>`.
32
+ - `opa fmt -w npm/rules` — auto-fix форматування.
33
+ - `docs/adr/template-dir-concern-inventory.md` — додано 4 `ga.*` концерни з відміткою `✓` (мігровано); оновлено summary-числа (85 концернів, 39 з template — 46%); додано секцію прогресу міграції.
34
+
35
+ ## [1.13.9] - 2026-05-17
36
+
37
+ ### Added
38
+
39
+ - `ga` rule template/ міграція (Phase 2): 4 концерни — `package_json` (contains-style), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object), `zizmor_yml` (snippet з канонічним path `rules.unpinned-uses.config.policies."*"`).
40
+ - Drift-тести (`test_data_template_drives_*`) у кожному `*_test.rego` ловлять регресію, якщо rego перестане читати з `data.template`.
41
+
42
+ ### Changed
43
+
44
+ - `ga.package_json.rego` — замість двох inline-deny з `is_string` + `regex.match` тепер один generic contains-walker через `data.template.contains`.
45
+ - `ga.vscode_extensions.rego` — замість inline `"github.vscode-github-actions"` тепер subset-of через `data.template.snippet.recommendations`.
46
+ - `ga.vscode_settings.rego` — 2-рівневий snippet-walker через `data.template.snippet` (літеральні keys `[github-actions-workflow]`, `editor.defaultFormatter`).
47
+ - `ga.zizmor_yml.rego` — замість substring `json.marshal` хака тепер структурний чек `rules.unpinned-uses.config.policies."*"` із expected value з `data.template.snippet`.
48
+ - `ga.mdc` — inline `package.json` snippet і `zizmor.yml` snippet блоки замінено на markdown-посилання на template-файли; додано посилання на нові template/ для `.vscode/{extensions,settings}.json`.
49
+
50
+ ## [1.13.8] - 2026-05-17
51
+
52
+ ### Changed
53
+
54
+ - Перенесено частину per-document логіки з `fix` у Rego policy:
55
+ - `js-lint`: `.jscpd.json` і `.vscode/extensions.json`;
56
+ - `ga`: `package.json#scripts.lint-ga`, `.vscode/extensions.json`, `.vscode/settings.json`, `.github/zizmor.yml`;
57
+ - `security`: `.gitleaks.toml` (`[extend].useDefault = true`);
58
+ - `vue`: залежності Vue/Vite-пакетів і заборону `esbuild`.
59
+ - Відповідні JS check-и спрощено до FS/cross-file/AST/tooling частини без дублювання Rego-умов.
60
+ - `ensureNitraCursorInRootDevDependencies` тепер додає `@nitra/cursor` тільки в `package.json` поруч із запуском, якщо в ньому є `workspaces`.
61
+ - `vue.mdc` уточнює тестування через Bun Test Runner + Vue Test Utils/happy-dom замість Vitest/jsdom.
62
+
63
+ ### Fixed
64
+
65
+ - `npm/package.json#devDependencies` — прибрано self-reference `@nitra/cursor`, щоб published package знову відповідав `npm-module` compact-package canon.
66
+
67
+ ## [1.13.7] - 2026-05-17
68
+
69
+ ### Fixed
70
+
71
+ - `inlineTemplateLinks`: `String.replace(needle, replacement)` інтерпретує `$'`, `$&` тощо у `replacement`. Через це інлайнінг `.gitleaks.toml.snippet.toml` (де є `$'''`) ламав вивід — хвіст `.mdc` реінжектився всередину блока. Перехід на function-replacer (`(_) => replacement`) усуває це. Додано регресійний тест із фікстурою `with-dollar.toml`.
72
+
73
+ ## [1.13.6] - 2026-05-17
74
+
75
+ ### Added
76
+
77
+ - `npm/scripts/utils/inline-template-links.mjs` — `inlineTemplateLinks(text, ruleDir)`: під час sync знаходить markdown-лінки виду `[label](./…/template/…)` у `.mdc` і замінює їх inline fenced-блоком з вмістом відповідного файла. Відсутній файл — hard error (fail loud).
78
+
79
+ ### Changed
80
+
81
+ - `readBundledRuleContent` у `npm/bin/n-cursor.js` тепер пропускає текст правила через `inlineTemplateLinks` перед записом у `.cursor/rules/n-*.mdc`. Template-посилання у скопійованих правилах більше не зламані.
82
+
83
+ ## [1.13.5] - 2026-05-17
84
+
85
+ ### Added
86
+
87
+ - Оркестратор `run-rule.mjs` тепер викликає `findMissingMdcRefs` для кожного правила — fail, якщо файл у `template/` не згаданий як markdown-посилання у `<id>.mdc`. Поки що активно лише для `security` (єдине правило з `template/`); готова страховка для Phase 2+.
88
+
89
+ ### Fixed
90
+
91
+ - `check-mdc-template-refs.test.mjs` тест 3 — фіксував дубль test 1; тепер використовує окрему `no-templates` фікстуру, що дійсно валідує "no template/ dirs → empty result".
92
+
93
+ ## [1.13.4] - 2026-05-17
94
+
95
+ ### Removed
96
+
97
+ - `npm/package.json#devDependencies` — повторно видалено self-reference `@nitra/cursor` (порушує canon `npm-module`: «devDependencies не публікуються користувачам пакета»). Автоматично повертався у попередніх тасках template-dir роботи; цей коміт остаточно прибирає.
98
+
99
+ ## [1.13.3] - 2026-05-17
100
+
101
+ ### Changed
102
+
103
+ - `security/security.mdc` — прибрано inline merge-фрагменти (package.json snippet для `lint-security`, .gitleaks.toml повний канон), замість них markdown-посилання на файли в `template/` (single source of truth). Зміст правила залишається (описи для чого потрібен gitleaks, GitHub Actions), видалено дублювання фіксованого коду.
104
+
7
105
  ## [1.13.2] - 2026-05-17
8
106
 
9
107
  ### Changed
@@ -20,6 +118,10 @@
20
118
 
21
119
  ### Changed
22
120
 
121
+ - `security/fix/gitleaks/check.mjs` читає канон з `template/`, не з inline regex.
122
+ - `security/policy/package_json/package_json.rego` читає очікувані значення з `data.template.*`, не з inline literals.
123
+ - Оркестратор `run-rule.mjs` для policy-концернів вантажить `template/` через `resolveConcernTemplateData` і передає у `runConftestBatch.templateData`.
124
+ - Снепет `.gitleaks.toml.snippet.toml` тримає канонічний title + allowlist paths (description лишається user-specific).
23
125
  - **9 правил переведено з `alwaysApply: true` на `alwaysApply: false` + `globs:`** — AI-контекст у Cursor/Claude Code підвантажується лише при роботі з релевантними файлами; програмна валідація через `npx check <rule>` залишається повністю функціональною незалежно від AI-контексту. Економить контекстне вікно у сесіях, де редагують код, далекий від відповідних конфігів.
24
126
  - **`bun`** (`1.7 → 1.8`) — `globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"`
25
127
  - **`capacitor`** (`1.0 → 1.1`) — `globs: "**/capacitor.config.json,**/android/**,**/ios/**"`
package/bin/n-cursor.js CHANGED
@@ -67,6 +67,7 @@ import { cwd, env } from 'node:process'
67
67
  import { fileURLToPath } from 'node:url'
68
68
 
69
69
  import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
70
+ import { inlineTemplateLinks } from '../scripts/utils/inline-template-links.mjs'
70
71
  import {
71
72
  detectAutoRules,
72
73
  detectLegacyRuleIds,
@@ -394,7 +395,7 @@ function normalizeRuleName(ruleName) {
394
395
  * @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
395
396
  * @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
396
397
  */
397
- function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
398
+ async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
398
399
  const id = normalizeRuleName(rule)
399
400
  const bundledPath = join(bundledRulesDir, id, `${id}.mdc`)
400
401
  if (!existsSync(bundledPath)) {
@@ -402,7 +403,8 @@ function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
402
403
  `Немає файлу ${id}/${id}.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
403
404
  )
404
405
  }
405
- return readFile(bundledPath, 'utf8')
406
+ const text = await readFile(bundledPath, 'utf8')
407
+ return inlineTemplateLinks(text, dirname(bundledPath))
406
408
  }
407
409
 
408
410
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.2",
3
+ "version": "1.13.11",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -36,7 +36,8 @@
36
36
  "!**/*.test.mjs",
37
37
  "!**/*_test.rego",
38
38
  "!**/test-helpers.mjs",
39
- "!**/fixtures/**"
39
+ "!**/fixtures/**",
40
+ "!**/__fixtures__/**"
40
41
  ],
41
42
  "type": "module",
42
43
  "types": "./types/bin/n-cursor.d.ts",
@@ -47,6 +48,7 @@
47
48
  "dependencies": {
48
49
  "oxc-parser": "^0.128.0",
49
50
  "picomatch": "^4.0.4",
51
+ "smol-toml": "^1.6.1",
50
52
  "yaml": "^2.8.3"
51
53
  },
52
54
  "engines": {
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Перевіряє GitHub Actions за правилом ga.mdc.
3
3
  *
4
- * Workflows лише з розширенням `.yml`, наявність clean/lint workflow, конфіг zizmor з ref-pin,
5
- * відсутність MegaLinter, коректний скрипт `lint-ga` у `package.json`, виклик у `lint-ga.yml`,
4
+ * Workflows лише з розширенням `.yml`, наявність clean/lint workflow,
5
+ * відсутність MegaLinter, виклик у `lint-ga.yml`,
6
6
  * наявність composite `.github/actions/setup-bun-deps/action.yml` (його записує npx `\@nitra/cursor`),
7
- * `\.vscode/settings.json` — `editor.defaultFormatter` **oxc** для `[github-actions-workflow]`.
8
7
  *
9
8
  * Структурні поля 4 канонічних workflow (`clean-ga-workflows.yml`, `clean-merged-branch.yml`,
10
9
  * `lint-ga.yml`, `git-ai.yml`) і УНІВЕРСАЛЬНІ перевірки для всіх `.github/workflows/*.yml`
11
10
  * (`concurrency`, заборонені `oven-sh/setup-bun` / `actions/cache` / `bun install` у `uses`/`run`,
12
11
  * shell-продовження `\` у `run`, обов'язковий `actions/checkout@v6` перед локальним
13
- * `setup-bun-deps`) у Rego-полісі під `npm/policy/ga/` і запускаються через
14
- * `bun run lint-ga` (`runConftestStep` у `lint-ga.mjs`). Тут лишилася лише git-залежна
15
- * перевірка `on.*.paths` glob-ів через `git ls-files :(glob)`.
12
+ * `setup-bun-deps`), а також `package.json`, `.vscode/*` і `.github/zizmor.yml`
13
+ * у Rego-полісі під `npm/policy/ga/`. Тут лишилися FS/git/tooling перевірки:
14
+ * наявність файлів, MegaLinter leftovers, `on.*.paths` через `git ls-files :(glob)`,
15
+ * і локальний `shellcheck`.
16
16
  */
17
17
  import { existsSync } from 'node:fs'
18
18
  import { readdir, readFile } from 'node:fs/promises'
@@ -30,8 +30,6 @@ const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/
30
30
  /** Типові конфіги MegaLinter у корені репо */
31
31
  const MEGALINTER_CONFIG_NAMES = ['.mega-linter.yml', '.megalinter.yaml', '.mega-linter.yaml']
32
32
 
33
- const N_CURSOR_LINT_GA_RE = /\bn-cursor\s+lint-ga\b/
34
-
35
33
  /** Обовʼязкові workflow-файли (ga.mdc). */
36
34
  const REQUIRED_WORKFLOWS = ['clean-ga-workflows.yml', 'clean-merged-branch.yml', 'lint-ga.yml', 'git-ai.yml']
37
35
 
@@ -179,92 +177,6 @@ async function checkMegalinter(wfDir, ymlWorkflows, passFn, failFn) {
179
177
  if (!found) passFn('Залишків MegaLinter не виявлено')
180
178
  }
181
179
 
182
- /**
183
- * Перевіряє zizmor конфіг.
184
- * @param {(msg: string) => void} passFn callback при успішній перевірці
185
- * @param {(msg: string) => void} failFn callback при помилці
186
- */
187
- async function checkZizmor(passFn, failFn) {
188
- const zizmorPath = '.github/zizmor.yml'
189
- if (!existsSync(zizmorPath)) {
190
- failFn(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
191
- return
192
- }
193
- const z = await readFile(zizmorPath, 'utf8')
194
- passFn(`${zizmorPath} існує`)
195
- if (z.includes('ref-pin')) {
196
- passFn(`${zizmorPath} містить політику ref-pin (zizmor)`)
197
- } else {
198
- failFn(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
199
- }
200
- }
201
-
202
- /**
203
- * Перевіряє `.vscode/settings.json`: oxfmt/oxc як default formatter для GitHub Actions workflow (мова
204
- * `github-actions-workflow` з розширення github.vscode-github-actions), узгоджено з oxc для yaml/workflow.
205
- * @param {(msg: string) => void} passFn callback при успішній перевірці
206
- * @param {(msg: string) => void} failFn callback при помилці
207
- */
208
- async function checkVscodeSettingsForGa(passFn, failFn) {
209
- const rel = '.vscode/settings.json'
210
- if (!existsSync(rel)) {
211
- failFn(`${rel} не існує — додай [github-actions-workflow].editor.defaultFormatter = oxc.oxc-vscode (ga.mdc)`)
212
- return
213
- }
214
- let settings
215
- try {
216
- settings = JSON.parse(await readFile(rel, 'utf8'))
217
- } catch {
218
- failFn(`${rel}: невалідний JSON (ga.mdc)`)
219
- return
220
- }
221
- if (!settings || typeof settings !== 'object') {
222
- failFn(`${rel}: очікується об’єкт налаштувань (ga.mdc)`)
223
- return
224
- }
225
- const block = /** @type {Record<string, unknown>} */ (settings)['[github-actions-workflow]']
226
- if (!block || typeof block !== 'object' || block === null || Array.isArray(block)) {
227
- failFn(`${rel}: додай "[github-actions-workflow]": { "editor.defaultFormatter": "oxc.oxc-vscode" } (ga.mdc)`)
228
- return
229
- }
230
- const df = String(/** @type {Record<string, unknown>} */ (block)['editor.defaultFormatter'] ?? '')
231
- if (df !== 'oxc.oxc-vscode') {
232
- failFn(
233
- `${rel}: [github-actions-workflow].editor.defaultFormatter має бути "oxc.oxc-vscode" (зараз: ${df || '∅'}) (ga.mdc)`
234
- )
235
- return
236
- }
237
- passFn(`${rel}: [github-actions-workflow] → oxc.oxc-vscode`)
238
- }
239
-
240
- /**
241
- * Перевіряє скрипт lint-ga в package.json.
242
- * @param {(msg: string) => void} passFn callback при успішній перевірці
243
- * @param {(msg: string) => void} failFn callback при помилці
244
- */
245
- async function checkLintGaScript(passFn, failFn) {
246
- if (!existsSync('package.json')) {
247
- failFn('package.json не існує — потрібен lint-ga у scripts')
248
- return
249
- }
250
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
251
- const lg = pkg.scripts?.['lint-ga']
252
- if (typeof lg !== 'string') {
253
- failFn('package.json: додай скрипт "lint-ga" (ga.mdc)')
254
- return
255
- }
256
- passFn('package.json містить lint-ga')
257
- // Канонічний скрипт делегує виконання CLI `n-cursor lint-ga` (bin з `@nitra/cursor`) — там preflight
258
- // на shellcheck + послідовно `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
259
- // Виклик через bin-ім’я `n-cursor`, а не `npx --no @nitra/cursor`, бо `bun run` транслює `npx` у `bun x`,
260
- // а `bun x @nitra/cursor` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання.
261
- if (N_CURSOR_LINT_GA_RE.test(lg)) {
262
- passFn('lint-ga делегує CLI n-cursor lint-ga (preflight shellcheck + actionlint + zizmor)')
263
- } else {
264
- failFn('lint-ga має бути "n-cursor lint-ga" — CLI робить preflight shellcheck перед actionlint/zizmor (ga.mdc)')
265
- }
266
- }
267
-
268
180
  /**
269
181
  * Перевіряє наявність локального `shellcheck` у PATH. `actionlint` (`bunx github-actionlint`)
270
182
  * запускає shell-перевірки в кроках `run:` workflow тільки коли `shellcheck` доступний; інакше
@@ -437,19 +349,6 @@ export async function check() {
437
349
  await checkApplyWorkflow(wfDir, files, 'apply-k8s.yml', '**/k8s/**/*.yaml', pass, fail)
438
350
  await checkApplyWorkflow(wfDir, files, 'apply-nats-consumer.yml', '**/consumer.yaml', pass, fail)
439
351
 
440
- if (existsSync('.vscode/extensions.json')) {
441
- const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
442
- if (ext.recommendations?.includes('github.vscode-github-actions')) {
443
- pass('extensions.json містить github.vscode-github-actions')
444
- } else {
445
- fail('extensions.json не містить github.vscode-github-actions')
446
- }
447
- } else {
448
- fail('.vscode/extensions.json не існує')
449
- }
450
-
451
- await checkVscodeSettingsForGa(pass, fail)
452
-
453
352
  await checkMegalinter(wfDir, ymlWorkflows, pass, fail)
454
353
 
455
354
  // git-залежна перевірка `on.push.paths` glob-ів (вимагає `git ls-files`) —
@@ -462,8 +361,6 @@ export async function check() {
462
361
  }
463
362
  }
464
363
 
465
- await checkZizmor(pass, fail)
466
- await checkLintGaScript(pass, fail)
467
364
  checkShellcheckInstalled(pass, fail)
468
365
 
469
366
  return reporter.getExitCode()
package/rules/ga/ga.mdc CHANGED
@@ -242,11 +242,7 @@ jobs:
242
242
 
243
243
  **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Канонічний скрипт у корені делегує виконання CLI `n-cursor lint-ga` (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`), який робить preflight на `shellcheck` і послідовно запускає `actionlint` та `zizmor`:
244
244
 
245
- ```json title="package.json"
246
- "scripts": {
247
- "lint-ga": "n-cursor lint-ga"
248
- }
249
- ```
245
+ - `package.json` — `scripts.lint-ga` має містити `n-cursor lint-ga`: [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
250
246
 
251
247
  > Не використовуй `npx --no @nitra/cursor lint-ga` — `bun run` автоматично транслює `npx` у `bun x`, а `bun x` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання. Виклик через bin-ім’я `n-cursor` працює і у `bun run`, і у `npm run`.
252
248
 
@@ -254,16 +250,15 @@ CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, поті
254
250
 
255
251
  **`.github/zizmor.yml`:** для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection):
256
252
 
257
- ```yaml title=".github/zizmor.yml"
258
- # https://docs.zizmor.sh/configuration/
259
- rules:
260
- unpinned-uses:
261
- config:
262
- policies:
263
- '*': ref-pin
264
- template-injection:
265
- disable: true
266
- ```
253
+ - Канон `.github/zizmor.yml`: [zizmor.yml.snippet.yml](./policy/zizmor_yml/template/zizmor.yml.snippet.yml)
254
+
255
+ **`.vscode/extensions.json`** має рекомендувати `github.vscode-github-actions`:
256
+
257
+ - Канон: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
258
+
259
+ **`.vscode/settings.json`** для мови `github-actions-workflow` має `editor.defaultFormatter = "oxc.oxc-vscode"`:
260
+
261
+ - Канон: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
267
262
 
268
263
  **MegaLinter:** не використовувати; прибрати workflow, конфіги (`.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`), залежності та згадки в CI / pre-commit / документації.
269
264
 
@@ -0,0 +1,20 @@
1
+ # Перевірка кореневого `package.json` для GitHub Actions tooling (ga.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "contains": ... } }
4
+ # Структура --data сформована з template/package.json.contains.json.
5
+ #
6
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
7
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
8
+ package ga.package_json
9
+
10
+ import rego.v1
11
+
12
+ # Кожне рядкове поле з contains має містити кожен substring.
13
+ # Відсутність ключа → `""` → contains() = false → deny.
14
+ deny contains msg if {
15
+ some script_name, needles in data.template.contains.scripts
16
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
17
+ some needle in needles
18
+ not contains(actual, needle)
19
+ msg := sprintf("package.json: scripts.%s має містити %q (ga.mdc)", [script_name, needle])
20
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": "package.json",
5
+ "required": true
6
+ },
7
+ "missingMessage": "package.json не існує — потрібен lint-ga у scripts (ga.mdc)"
8
+ }
@@ -0,0 +1 @@
1
+ { "scripts": { "lint-ga": ["n-cursor lint-ga"] } }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": ".vscode/extensions.json",
5
+ "required": true
6
+ },
7
+ "missingMessage": ".vscode/extensions.json не існує — додай github.vscode-github-actions (ga.mdc)"
8
+ }
@@ -0,0 +1 @@
1
+ { "recommendations": ["github.vscode-github-actions"] }
@@ -0,0 +1,18 @@
1
+ # Перевірка `.vscode/extensions.json` для GitHub Actions (ga.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/extensions.json.snippet.json.
5
+ # `recommendations` — subset-of: кожна рекомендація з template має бути у input.
6
+ # Додаткові рекомендації від інших правил дозволені.
7
+ #
8
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
9
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
10
+ package ga.vscode_extensions
11
+
12
+ import rego.v1
13
+
14
+ deny contains msg if {
15
+ some rec in data.template.snippet.recommendations
16
+ not rec in {r | some r in object.get(input, "recommendations", [])}
17
+ msg := sprintf(".vscode/extensions.json: recommendations має містити %q (ga.mdc)", [rec])
18
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": ".vscode/settings.json",
5
+ "required": true
6
+ },
7
+ "missingMessage": ".vscode/settings.json не існує — додай [github-actions-workflow].editor.defaultFormatter (ga.mdc)"
8
+ }
@@ -0,0 +1 @@
1
+ { "[github-actions-workflow]": { "editor.defaultFormatter": "oxc.oxc-vscode" } }
@@ -0,0 +1,22 @@
1
+ # Перевірка `.vscode/settings.json` для GitHub Actions workflow (ga.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/settings.json.snippet.json.
5
+ # Snippet — 2-рівнева мапа: <language-block-key>.<setting-key> = <expected>
6
+ # (VS Code-конвенція: ключі типу `[github-actions-workflow]` і `editor.defaultFormatter`
7
+ # — це літеральні string-keys із дужками/крапкою, не вкладені обʼєкти).
8
+ #
9
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
10
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
11
+ package ga.vscode_settings
12
+
13
+ import rego.v1
14
+
15
+ deny contains msg if {
16
+ some block_key, expected_inner in data.template.snippet
17
+ inner := object.get(input, block_key, {})
18
+ some leaf_key, expected_value in expected_inner
19
+ actual := object.get(inner, leaf_key, null)
20
+ actual != expected_value
21
+ msg := sprintf(".vscode/settings.json: %s.%s має бути %q (ga.mdc)", [block_key, leaf_key, expected_value])
22
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": ".github/zizmor.yml",
5
+ "required": true
6
+ },
7
+ "missingMessage": ".github/zizmor.yml не існує — потрібен для zizmor (ga.mdc)"
8
+ }
@@ -0,0 +1,5 @@
1
+ rules:
2
+ unpinned-uses:
3
+ config:
4
+ policies:
5
+ '*': ref-pin
@@ -0,0 +1,27 @@
1
+ # Перевірка `.github/zizmor.yml` для GitHub Actions (ga.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/zizmor.yml.snippet.yml.
5
+ # Канонічний шлях — `rules.unpinned-uses.config.policies."*"`; expected value
6
+ # (наприклад `"ref-pin"`) приходить із template, path лишається тут.
7
+ #
8
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
9
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
10
+ package ga.zizmor_yml
11
+
12
+ import rego.v1
13
+
14
+ deny contains msg if {
15
+ expected := data.template.snippet.rules["unpinned-uses"].config.policies["*"]
16
+ policies := object.get(
17
+ object.get(
18
+ object.get(object.get(input, "rules", {}), "unpinned-uses", {}),
19
+ "config",
20
+ {},
21
+ ),
22
+ "policies",
23
+ {},
24
+ )
25
+ object.get(policies, "*", null) != expected
26
+ msg := sprintf(".github/zizmor.yml: rules.unpinned-uses.config.policies[%q] має бути %q (ga.mdc)", ["*", expected])
27
+ }
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * Перевіряє лінт JavaScript за правилом js-lint.mdc.
3
3
  *
4
- * Канонічний `lint-js`, flat ESLint з getConfig і ignore для auto-imports, рекомендації VSCode,
4
+ * Flat ESLint з getConfig і ignore для auto-imports,
5
5
  * `.oxlintrc.json` має збігатися з каноном oxlint у пакеті (`npm/scripts/utils/oxlint-canonical.json`):
6
6
  * plugins, jsPlugins, categories, усі правила з канону (додаткові записи в `rules` дозволені), settings, env,
7
- * globals, ignorePatterns. `@nitra/eslint-config` у devDependencies мінімум **3.9.2** 3.8.0 правило
8
- * `no-restricted-syntax` для `ForInStatement` забороняє `for...in`; з 3.9.2 у `getConfig` вбудовано
9
- * ignore для ADR-каталогів — локально цей glob додавати не потрібно; також тягне транзитивний
10
- * `@e18e/eslint-plugin` для oxlint), `.jscpd.json` (gitignore, exitCode, reporters, minLines), workflow
11
- * `lint-js.yml` (checkout@v6, setup-bun-deps, bunx без --fix), без prettier, `engines.node` >= 24,
12
- * `engines.bun` >= 1.3, `"type": "module"` у кореневому і всіх workspace `package.json`. Дубль перевірки JS у `lint.yml` — заборонено.
7
+ * globals, ignorePatterns. Також перевіряє workspace `package.json` на `type: "module"`
8
+ * і `engines`, workflow-дубль у `lint.yml`, `knip.json` autofill і застарілі `.eslintrc*`.
9
+ *
10
+ * Per-document вимоги (`lint-js`, `@nitra/eslint-config`, root `engines`, `.jscpd.json`,
11
+ * `.vscode/extensions.json`, `lint-js.yml`) у policy-пакетах `js_lint.*`.
13
12
  */
14
13
  import { existsSync } from 'node:fs'
15
14
  import { copyFile, readFile } from 'node:fs/promises'
@@ -42,9 +41,6 @@ export const KNIP_CANONICAL_JSON_PATH = join(
42
41
  'knip-canonical.json'
43
42
  )
44
43
 
45
- /** Мінімальні рекомендації розширень редактора з js-lint.mdc (eslint, oxlint, GA). */
46
- export const REQUIRED_VSCODE_EXTENSIONS = ['dbaeumer.vscode-eslint', 'github.vscode-github-actions', 'oxc.oxc-vscode']
47
-
48
44
  const NON_DIGITS_RE = /\D+/u
49
45
 
50
46
  // Канонічний рядок `lint-js`-скрипта і мінімальна версія `@nitra/eslint-config` —
@@ -353,36 +349,6 @@ async function checkOxlintRc(passFn, failFn) {
353
349
  }
354
350
  }
355
351
 
356
- /**
357
- * Перевіряє .vscode/extensions.json на потрібні розширення.
358
- * @param {(msg: string) => void} passFn callback при успішній перевірці
359
- * @param {(msg: string) => void} failFn callback при помилці
360
- */
361
- async function checkVscodeExtensions(passFn, failFn) {
362
- if (!existsSync('.vscode/extensions.json')) {
363
- failFn('.vscode/extensions.json не існує — додай recommendations з js-lint.mdc (див. check-js-lint.mjs)')
364
- return
365
- }
366
- let ext
367
- try {
368
- ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
369
- } catch {
370
- failFn('.vscode/extensions.json не є валідним JSON')
371
- return
372
- }
373
- const rec = ext.recommendations
374
- if (!Array.isArray(rec)) {
375
- failFn('.vscode/extensions.json: поле recommendations має бути масивом')
376
- return
377
- }
378
- const missing = REQUIRED_VSCODE_EXTENSIONS.filter(id => !rec.includes(id))
379
- if (missing.length > 0) {
380
- failFn(`.vscode/extensions.json: додай у recommendations: ${missing.join(', ')} (мінімум для js-lint.mdc)`)
381
- } else {
382
- passFn('.vscode/extensions.json: є рекомендації oxlint, eslint і GitHub Actions')
383
- }
384
- }
385
-
386
352
  /**
387
353
  * FS-existence для `lint-js.yml` + cross-file перевірка, що `lint.yml` (якщо існує)
388
354
  * не дублює лінт JS-кроки. Структуру `lint-js.yml` (`actions/checkout@v6`,
@@ -408,47 +374,6 @@ async function checkLintJsWorkflows(passFn, failFn) {
408
374
  }
409
375
  }
410
376
 
411
- /**
412
- * Перевіряє .jscpd.json.
413
- * @param {(msg: string) => void} passFn callback при успішній перевірці
414
- * @param {(msg: string) => void} failFn callback при помилці
415
- */
416
- async function checkJscpdConfig(passFn, failFn) {
417
- if (!existsSync('.jscpd.json')) {
418
- failFn('.jscpd.json не існує — створи з полями згідно check js-lint')
419
- return
420
- }
421
- let jscpdCfg
422
- try {
423
- jscpdCfg = JSON.parse(await readFile('.jscpd.json', 'utf8'))
424
- } catch {
425
- failFn('.jscpd.json не є валідним JSON')
426
- return
427
- }
428
- passFn('.jscpd.json існує')
429
- if (jscpdCfg.gitignore === true) {
430
- passFn('.jscpd.json: gitignore увімкнено')
431
- } else {
432
- failFn('.jscpd.json має містити "gitignore": true')
433
- }
434
- if (jscpdCfg.exitCode === 1) {
435
- passFn('.jscpd.json: exitCode 1 при дублікатах')
436
- } else {
437
- failFn('.jscpd.json має містити "exitCode": 1 (інакше CI не впаде на клонах)')
438
- }
439
- if (Array.isArray(jscpdCfg.reporters) && jscpdCfg.reporters.includes('console')) {
440
- passFn('.jscpd.json: reporters містить console')
441
- } else {
442
- failFn('.jscpd.json має містити "reporters": ["console"] (або масив із "console")')
443
- }
444
- const minLines = jscpdCfg.minLines
445
- if (typeof minLines === 'number' && minLines >= 25) {
446
- passFn(`.jscpd.json: minLines ${minLines} (>=25)`)
447
- } else {
448
- failFn('.jscpd.json має містити "minLines" як число >= 25')
449
- }
450
- }
451
-
452
377
  /**
453
378
  * Перевіряє наявність `knip.json` у корені проєкту. Якщо файл відсутній —
454
379
  * копіює канонічний `knip-canonical.json` з пакета `@nitra/cursor` як стартовий
@@ -485,9 +410,7 @@ export async function check() {
485
410
  await checkEslintConfig(pass, fail)
486
411
  await checkPackageJsonJsLint(pass, fail)
487
412
  await checkOxlintRc(pass, fail)
488
- await checkVscodeExtensions(pass, fail)
489
413
  await checkLintJsWorkflows(pass, fail)
490
- await checkJscpdConfig(pass, fail)
491
414
  await checkKnipConfig(pass, fail)
492
415
 
493
416
  for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {