@nitra/cursor 12.8.1 → 12.8.2

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 (47) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +1 -1
  3. package/rules/feedback/feedback.mdc +3 -3
  4. package/rules/ga/ga.mdc +1 -1
  5. package/rules/ga/policy/workflow_common/workflow_common.rego +3 -3
  6. package/rules/js/coverage/coverage.mjs +7 -7
  7. package/rules/js/docs/fix.md +1 -1
  8. package/rules/js/docs/index.md +3 -3
  9. package/rules/js/docs/main.md +1 -1
  10. package/rules/js/js/check.mjs +10 -10
  11. package/rules/js/js/docs/check.md +3 -3
  12. package/rules/js/js/docs/index.md +3 -3
  13. package/rules/js/js/docs/lint-findings.md +1 -1
  14. package/rules/js/js/docs/tooling.md +1 -1
  15. package/rules/js/js/docs/utils_imports.md +8 -8
  16. package/rules/js/js/tooling.mjs +1 -1
  17. package/rules/js/js/utils_imports.mjs +3 -3
  18. package/rules/js/js.mdc +30 -6
  19. package/rules/js/main.mjs +22 -5
  20. package/rules/js/policy/jscpd/jscpd.rego +4 -4
  21. package/rules/js/policy/jscpd/target.json +1 -1
  22. package/rules/js/policy/lint_js_yml/lint_js_yml.rego +6 -6
  23. package/rules/js/policy/lint_js_yml/template/lint-js.yml.snippet.yml +1 -1
  24. package/rules/js/policy/package_json/package_json.rego +7 -7
  25. package/rules/js/policy/vscode_extensions/target.json +1 -1
  26. package/rules/js/policy/vscode_extensions/vscode_extensions.rego +2 -2
  27. package/rules/js-run/lib/docs/conn-file-rules.md +1 -1
  28. package/rules/style-lint/js/tooling.mjs +1 -1
  29. package/rules/test/js/docs/stryker_config.md +1 -1
  30. package/rules/test/js/stryker_config.mjs +4 -4
  31. package/rules/test/js/vitest-config-pool-forks.mjs +1 -1
  32. package/rules/test/test.mdc +4 -4
  33. package/scripts/lib/docs/discover-checkable-rules.md +2 -2
  34. package/scripts/lib/gha-workflow.mjs +1 -1
  35. package/scripts/lib/timing-summary.mjs +1 -1
  36. package/scripts/sync-setup-bun-deps-action.mjs +1 -1
  37. package/scripts/utils/resolve-js-root.mjs +1 -1
  38. package/skills/coverage-fix/meta.json +1 -1
  39. package/skills/lint/SKILL.md +2 -2
  40. package/skills/llm-patch/SKILL.md +1 -1
  41. package/rules/js-lint-ci/docs/fix.md +0 -28
  42. package/rules/js-lint-ci/docs/index.md +0 -12
  43. package/rules/js-lint-ci/docs/main.md +0 -27
  44. package/rules/js-lint-ci/js/docs/index.md +0 -11
  45. package/rules/js-lint-ci/js-lint-ci.mdc +0 -45
  46. package/rules/js-lint-ci/main.mjs +0 -33
  47. package/rules/js-lint-ci/meta.json +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.8.2] - 2026-06-22
4
+
5
+ ### Changed
6
+
7
+ - ♻️ refactor(docs): Оновлення логіки та формату в документації
8
+
3
9
  ## [12.8.1] - 2026-06-22
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.8.1",
3
+ "version": "12.8.2",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -29,7 +29,7 @@ version: '1.0'
29
29
  Кожен пункт — за схемою:
30
30
 
31
31
  - **target** — `rule` | `skill` | `check`
32
- - **id** — який саме (`lint`, `text`, `js-lint`, …)
32
+ - **id** — який саме (`lint`, `text`, `js`, …)
33
33
  - **kind** — `ambiguous-doc` | `missing-check` | `false-positive` | `no-autofix` | `recurring-pattern`
34
34
  - **evidence** — конкретний `файл:рядок` або вивід команди з цього запуску
35
35
  - **suggestion** — запропонована зміна
@@ -42,8 +42,8 @@ version: '1.0'
42
42
  - skill `n-lint`, `ambiguous-doc` — крок 2 не каже, як діяти при частковому autofix
43
43
  evidence: `oxlint --fix` лишив 3 no-autofix-помилки
44
44
  suggestion: додати в SKILL.md підпункт про частковий autofix
45
- - rule `js-lint`, `missing-check` — немає програмної перевірки jscpd-порогу
46
- evidence: jscpd впав, але `check js-lint` цього не ловить
45
+ - rule `js`, `missing-check` — немає програмної перевірки jscpd-порогу
46
+ evidence: jscpd впав, але `check js` цього не ловить
47
47
  ```
48
48
 
49
49
  ## Ефемерність — обовʼязково
package/rules/ga/ga.mdc CHANGED
@@ -138,4 +138,4 @@ CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, поті
138
138
 
139
139
  **MegaLinter:** не використовувати; прибрати workflow, конфіги (`.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`), залежності та згадки в CI / pre-commit / документації.
140
140
 
141
- **`depcheck`:** не використовувати у `.github/workflows/*.yml` — мігровано на `knip` (див. `js-lint.mdc`). Перевірка невикористаних залежностей виконується разом з рештою лінтерів у `lint-js`, окремий крок `npx depcheck` у workflow не потрібен і блокується полісі `ga.workflow_common`.
141
+ **`depcheck`:** не використовувати у `.github/workflows/*.yml` — мігровано на `knip` (див. `js.mdc`). Перевірка невикористаних залежностей виконується разом з рештою лінтерів у `lint-js`, окремий крок `npx depcheck` у workflow не потрібен і блокується полісі `ga.workflow_common`.
@@ -34,7 +34,7 @@ forbidden_step_substrings := {
34
34
  }
35
35
 
36
36
  # Заборонені бінарки у `run:` кроках (ga.mdc). `depcheck` мігровано на `knip`
37
- # у `lint-js.mdc` — окремий крок у workflow не потрібен. Регексп ловить виклики
37
+ # у `js.mdc` — окремий крок у workflow не потрібен. Регексп ловить виклики
38
38
  # через `npx`, `bunx`, `npm exec`, або як standalone-команду на початку рядка.
39
39
  forbidden_run_command_patterns := {"depcheck": `(?:^|[\s;&|])(?:npx|bunx|npm exec|pnpm exec)?[ \t]*depcheck\b`}
40
40
 
@@ -62,7 +62,7 @@ min_uses_version_template := concat(" ", [
62
62
 
63
63
  forbidden_run_command_template := concat(" ", [
64
64
  "jobs.%s.steps[%d]: `%s` заборонено у workflow —",
65
- "мігровано на knip (js-lint.mdc, ga.mdc)",
65
+ "мігровано на knip (js.mdc, ga.mdc)",
66
66
  ])
67
67
 
68
68
  # ── Аліаси на input ────────────────────────────────────────────────────────
@@ -90,7 +90,7 @@ deny contains msg if {
90
90
 
91
91
  # ── deny: depcheck у будь-якому `run:` ────────────────────────────────────
92
92
  #
93
- # `depcheck` мігровано на `knip` (js-lint.mdc); `knip` вже запускається у lint-js
93
+ # `depcheck` мігровано на `knip` (js.mdc); `knip` вже запускається у lint-js
94
94
  # CI як частина `bunx knip` у скрипті, тож окремий depcheck-крок зайвий і має
95
95
  # бути видалений з workflow-файлів.
96
96
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JS-провайдер для `n-cursor coverage`: збирає метрики покриття (`vitest run --coverage`)
3
3
  * і мутаційного тестування (Stryker з vitest-runner + perTest) для JS/TS коду.
4
- * Активується через `js-lint` правило в `.n-cursor.json#rules`; реальна applies-логіка
4
+ * Активується через `js` правило в `.n-cursor.json#rules`; реальна applies-логіка
5
5
  * — у `detect(cwd)`.
6
6
  *
7
7
  * Контракт провайдера — у docs/superpowers/specs/2026-05-24-coverage-rule-design.md.
@@ -42,7 +42,7 @@ export function scopeToRoot(changedFiles, cwd, jsRoot) {
42
42
  return out
43
43
  }
44
44
  const VITEST_HINT =
45
- 'js-lint coverage: vitest відсутній у package.json — додай `vitest`, `@vitest/coverage-v8` та `@stryker-mutator/vitest-runner` у devDependencies (див. test.mdc)'
45
+ 'js coverage: vitest відсутній у package.json — додай `vitest`, `@vitest/coverage-v8` та `@stryker-mutator/vitest-runner` у devDependencies (див. test.mdc)'
46
46
 
47
47
  /**
48
48
  * Чи у пакеті встановлено vitest (через dependencies або devDependencies).
@@ -334,7 +334,7 @@ async function collectOneRoot(jsRoot, cwd, runner, scope = null) {
334
334
  const mutateSrc = scope ? scope.files.filter(f => !TEST_FILE.test(f)) : null
335
335
 
336
336
  // 1. Coverage через vitest run --passWithNoTests --coverage (+ --changed у changed-режимі)
337
- const lcovDir = await mkdtemp(join(tmpdir(), 'js-lint-cov-'))
337
+ const lcovDir = await mkdtemp(join(tmpdir(), 'js-cov-'))
338
338
  let coverage
339
339
  try {
340
340
  const code = await runner.runJsCoverage(scope ? { cwd: jsRoot, lcovDir, base: scope.base } : { cwd: jsRoot, lcovDir })
@@ -364,7 +364,7 @@ async function collectOneRoot(jsRoot, cwd, runner, scope = null) {
364
364
  const mutationPath = join(jsRoot, 'reports', 'stryker', 'mutation.json')
365
365
  if (!existsSync(mutationPath)) {
366
366
  throw new Error(
367
- 'js-lint coverage: stryker не залишив mutation.json — ' +
367
+ 'js coverage: stryker не залишив mutation.json — ' +
368
368
  'запусти `npx @nitra/cursor fix test` для встановлення canonical stryker.config.mjs, ' +
369
369
  'або налаштуй його вручну'
370
370
  )
@@ -411,7 +411,7 @@ export async function collect(cwd, opts = {}) {
411
411
  const runner = opts.runner ?? defaultRunner
412
412
  const changed = Array.isArray(opts.changedFiles)
413
413
  const jsRoots = await resolveAllJsRoots(cwd)
414
- if (jsRoots.length === 0) throw new Error('js-lint coverage: package.json не знайдено')
414
+ if (jsRoots.length === 0) throw new Error('js coverage: package.json не знайдено')
415
415
 
416
416
  const results = []
417
417
  for (const jsRoot of jsRoots) {
@@ -429,9 +429,9 @@ export async function collect(cwd, opts = {}) {
429
429
  // Changed-режим: нема змінених JS у жодному root → тихо порожньо (це pass, не помилка).
430
430
  if (changed) return []
431
431
  console.error(
432
- 'js-lint coverage: жоден workspace не має тестів ' +
432
+ 'js coverage: жоден workspace не має тестів ' +
433
433
  '(`*.test.{js,mjs}` у `tests/` або поряд із джерелом) — ' +
434
- 'додай тести або вилучи `js-lint` з .n-cursor.json#rules'
434
+ 'додай тести або вилучи `js` з .n-cursor.json#rules'
435
435
  )
436
436
  return []
437
437
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: fix.mjs
4
- resource: npm/rules/js-lint/fix.mjs
4
+ resource: npm/rules/js/fix.mjs
5
5
  docgen:
6
6
  crc: 38cf876b
7
7
  score: 90
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  type: Directory Index
3
- title: npm/rules/js-lint
4
- resource: npm/rules/js-lint/
3
+ title: npm/rules/js
4
+ resource: npm/rules/js/
5
5
  ---
6
6
 
7
- # npm/rules/js-lint
7
+ # npm/rules/js
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: main.mjs
4
- resource: npm/rules/js-lint/main.mjs
4
+ resource: npm/rules/js/main.mjs
5
5
  docgen:
6
6
  crc: 4f68b557
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
@@ -24,7 +24,7 @@ async function checkEslintConfig(passFn, failFn, cwd) {
24
24
  eslintPath = 'eslint.config.mjs'
25
25
  passFn('eslint.config.mjs існує')
26
26
  } else {
27
- failFn('Відсутній eslint.config.js або eslint.config.mjs — flat config з getConfig (js-lint.mdc)')
27
+ failFn('Відсутній eslint.config.js або eslint.config.mjs — flat config з getConfig (js.mdc)')
28
28
  return
29
29
  }
30
30
  const eslintRaw = await readFile(join(cwd, eslintPath), 'utf8')
@@ -32,7 +32,7 @@ async function checkEslintConfig(passFn, failFn, cwd) {
32
32
  {
33
33
  needle: 'getConfig',
34
34
  ok: `${eslintPath}: містить getConfig`,
35
- err: `${eslintPath}: потрібен виклик getConfig (js-lint.mdc)`
35
+ err: `${eslintPath}: потрібен виклик getConfig (js.mdc)`
36
36
  },
37
37
  {
38
38
  needle: '@nitra/eslint-config',
@@ -42,7 +42,7 @@ async function checkEslintConfig(passFn, failFn, cwd) {
42
42
  {
43
43
  needle: '**/auto-imports.d.ts',
44
44
  ok: `${eslintPath}: ignores містить **/auto-imports.d.ts`,
45
- err: `${eslintPath}: додай у ignores запис **/auto-imports.d.ts (js-lint.mdc)`
45
+ err: `${eslintPath}: додай у ignores запис **/auto-imports.d.ts (js.mdc)`
46
46
  }
47
47
  ]
48
48
  for (const { needle, ok, err } of checks) {
@@ -71,7 +71,7 @@ function checkPackageJsonTypeModule(label, pkg, passFn, failFn) {
71
71
  if (pkg.type === 'module') {
72
72
  passFn(`${label}: "type": "module"`)
73
73
  } else {
74
- failFn(`${label}: має містити "type": "module" (js-lint.mdc)`)
74
+ failFn(`${label}: має містити "type": "module" (js.mdc)`)
75
75
  }
76
76
  }
77
77
 
@@ -163,7 +163,7 @@ async function checkPackageJsonJsLint(passFn, failFn, cwd) {
163
163
  async function checkOxlintRc(passFn, failFn, cwd) {
164
164
  const oxPath = join(cwd, '.oxlintrc.json')
165
165
  if (!existsSync(oxPath)) {
166
- failFn('.oxlintrc.json не існує — додай конфіг oxlint (js-lint.mdc)')
166
+ failFn('.oxlintrc.json не існує — додай конфіг oxlint (js.mdc)')
167
167
  return
168
168
  }
169
169
  let oxCfg
@@ -204,14 +204,14 @@ async function checkLintJsWorkflows(passFn, failFn, cwd) {
204
204
  if (existsSync(join(cwd, '.github/workflows/lint-js.yml'))) {
205
205
  passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor fix → js_lint.lint_js_yml)')
206
206
  } else {
207
- failFn('.github/workflows/lint-js.yml не існує — створи його (js-lint.mdc)')
207
+ failFn('.github/workflows/lint-js.yml не існує — створи його (js.mdc)')
208
208
  }
209
209
 
210
210
  const lintYmlPath = join(cwd, '.github/workflows/lint.yml')
211
211
  if (existsSync(lintYmlPath)) {
212
212
  const lintYml = await readFile(lintYmlPath, 'utf8')
213
213
  if (lintYml.includes('bunx oxlint') && lintYml.includes('bunx eslint') && lintYml.includes('jscpd')) {
214
- failFn('.github/workflows/lint.yml дублює кроки lint-js.yml — залиш один workflow на лінт JS (js-lint.mdc)')
214
+ failFn('.github/workflows/lint.yml дублює кроки lint-js.yml — залиш один workflow на лінт JS (js.mdc)')
215
215
  } else {
216
216
  passFn('.github/workflows/lint.yml не дублює oxlint/eslint/jscpd з lint-js.yml')
217
217
  }
@@ -223,7 +223,7 @@ async function checkLintJsWorkflows(passFn, failFn, cwd) {
223
223
  * копіює канонічний `knip-canonical.json` з пакета `@nitra/cursor` як стартовий
224
224
  * baseline; зміст подальших модифікацій локально не валідується (`entry` /
225
225
  * `project` / `ignore` / `ignoreDependencies` / `ignoreBinaries` дозволені
226
- * будь-які; це side effect — описано у js-lint.mdc).
226
+ * будь-які; це side effect — описано у js.mdc).
227
227
  * @param {(msg: string) => void} passFn callback при успішній перевірці
228
228
  * @param {(msg: string) => void} failFn callback при помилці
229
229
  * @param {string} cwd корінь репозиторію
@@ -242,11 +242,11 @@ async function checkKnipConfig(passFn, failFn, cwd) {
242
242
  return
243
243
  }
244
244
  await copyFile(KNIP_CANONICAL_JSON_PATH, knipPath)
245
- passFn('knip.json створено з канонічного npm/rules/js-lint/js/data/tooling/knip-canonical.json (js-lint.mdc)')
245
+ passFn('knip.json створено з канонічного npm/rules/js/js/data/tooling/knip-canonical.json (js.mdc)')
246
246
  }
247
247
 
248
248
  /**
249
- * Перевіряє відповідність проєкту правилам js-lint.mdc
249
+ * Перевіряє відповідність проєкту правилам js.mdc
250
250
  * @param {string} [cwd] корінь репозиторію
251
251
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
252
252
  */
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: check.mjs
4
- resource: npm/rules/js-lint/js/check.mjs
4
+ resource: npm/rules/js/js/check.mjs
5
5
  docgen:
6
6
  crc: f61768f2
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
@@ -10,7 +10,7 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль виконує перевірку конфігураційних файлів проєкту, забезпечуючи їхню відповідність встановленим стандартам. Він читає та аналізує конфігураційні файли, включаючи `package.json`, `.oxlintrc.json`, `knip.json`, `knip-canonical.json` та `.eslintrc.json`. Перевірка ґрунтується на логіці, визначеній у (js-lint.mdc) та (text.mdc). Функціонал реалізований у режимі лише читання, що означає відсутність змін у файловій системі чи базах даних. При виявленні проблем, система перехоплює помилки, працюючи у режимі fail-safe. Свідомо ігноруються шляхи `.github` та `.git`.
13
+ Модуль виконує перевірку конфігураційних файлів проєкту, забезпечуючи їхню відповідність встановленим стандартам. Він читає та аналізує конфігураційні файли, включаючи `package.json`, `.oxlintrc.json`, `knip.json`, `knip-canonical.json` та `.eslintrc.json`. Перевірка ґрунтується на логіці, визначеній у (js.mdc) та (text.mdc). Функціонал реалізований у режимі лише читання, що означає відсутність змін у файловій системі чи базах даних. При виявленні проблем, система перехоплює помилки, працюючи у режимі fail-safe. Свідомо ігноруються шляхи `.github` та `.git`.
14
14
 
15
15
  ## Поведінка
16
16
 
@@ -30,7 +30,7 @@ docgen:
30
30
 
31
31
  ## Публічний API
32
32
 
33
- check — забезпечує відповідність проєкту вимогам, описаним у js-lint.mdc.
33
+ check — забезпечує відповідність проєкту вимогам, описаним у js.mdc.
34
34
 
35
35
  ## Гарантії поведінки
36
36
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  type: Directory Index
3
- title: npm/rules/js-lint/js
4
- resource: npm/rules/js-lint/js/
3
+ title: npm/rules/js/js
4
+ resource: npm/rules/js/js/
5
5
  ---
6
6
 
7
- # npm/rules/js-lint/js
7
+ # npm/rules/js/js
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------------------------- | --------- |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: lint-findings.mjs
4
- resource: npm/rules/js-lint/js/lint-findings.mjs
4
+ resource: npm/rules/js/js/lint-findings.mjs
5
5
  docgen:
6
6
  crc: bee587da
7
7
  score: 100
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: tooling.mjs
4
- resource: npm/rules/js-lint/js/tooling.mjs
4
+ resource: npm/rules/js/js/tooling.mjs
5
5
  docgen:
6
6
  crc: 7ead48ee
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  type: JS Module
3
3
  title: utils_imports.mjs
4
- resource: npm/rules/js-lint/js/utils_imports.mjs
4
+ resource: npm/rules/js/js/utils_imports.mjs
5
5
  docgen:
6
6
  crc: 7eaeaf96
7
7
  ---
8
8
 
9
- Модуль `npm/rules/js-lint/js/utils_imports.mjs` реалізує одну з перевірок правила `js-lint.mdc`: жоден файл усередині будь-якого каталогу з ім'ям `utils/` не має імпортувати щось за межами цього самого каталогу через відносні шляхи з префіксом `..`.
9
+ Модуль `npm/rules/js/js/utils_imports.mjs` реалізує одну з перевірок правила `js.mdc`: жоден файл усередині будь-якого каталогу з ім'ям `utils/` не має імпортувати щось за межами цього самого каталогу через відносні шляхи з префіксом `..`.
10
10
 
11
11
  Філософія перевірки:
12
12
 
@@ -150,7 +150,7 @@ docgen:
150
150
 
151
151
  ### Інтеграція в перевірочний рантайм
152
152
 
153
- Модуль викликається check-runner-ом правила `js-lint.mdc` (зазвичай із `npm/rules/js-lint/js/`). Runner імпортує named export `check` і чекає на її resolved-значення як на process exit-code. Сам файл **не** має top-level executable коду — лише визначення; це дозволяє безпечно імпортувати його у тестах.
153
+ Модуль викликається check-runner-ом правила `js.mdc` (зазвичай із `npm/rules/js/js/`). Runner імпортує named export `check` і чекає на її resolved-значення як на process exit-code. Сам файл **не** має top-level executable коду — лише визначення; це дозволяє безпечно імпортувати його у тестах.
154
154
 
155
155
  Типовий виклик (псевдокод):
156
156
 
@@ -167,7 +167,7 @@ process.exit(exitCode)
167
167
  2. `check()` знаходить `utils/` каталоги в усіх package-roots.
168
168
  3. Для кожного non-test source файлу витягає імпорти.
169
169
  4. Жоден імпорт не починається з `..`.
170
- 5. `reporter.pass('utils-каталогів: N, перевірено M файлів — domain-bound імпортів немає (js-lint.mdc)')`.
170
+ 5. `reporter.pass('utils-каталогів: N, перевірено M файлів — domain-bound імпортів немає (js.mdc)')`.
171
171
  6. `getExitCode() → 0`.
172
172
 
173
173
  ### Сценарій "є порушення"
@@ -175,7 +175,7 @@ process.exit(exitCode)
175
175
  1. Знайдено файл `packages/foo/utils/helper.mjs`.
176
176
  2. У ньому є `import bar from '../lib/bar.mjs'`.
177
177
  3. `PARENT_RELATIVE_RE` матчить `../lib/bar.mjs`.
178
- 4. `reporter.fail('packages/foo/utils/helper.mjs: заборонений імпорт \'../lib/bar.mjs\' — utils/-файли мають бути generic (js-lint.mdc)')`.
178
+ 4. `reporter.fail('packages/foo/utils/helper.mjs: заборонений імпорт \'../lib/bar.mjs\' — utils/-файли мають бути generic (js.mdc)')`.
179
179
  5. `violations` інкрементується.
180
180
  6. По завершенню `getExitCode() → 1`.
181
181
 
@@ -183,7 +183,7 @@ process.exit(exitCode)
183
183
 
184
184
  1. У жодному package немає каталогу `utils/`.
185
185
  2. `utilsDirSet.size === 0`.
186
- 3. `reporter.pass('utils-каталогів немає — перевірку пропущено (js-lint.mdc)')`.
186
+ 3. `reporter.pass('utils-каталогів немає — перевірку пропущено (js.mdc)')`.
187
187
  4. `getExitCode() → 0`.
188
188
 
189
189
  ### Сценарій "файл із синтаксичною помилкою"
@@ -207,6 +207,6 @@ process.exit(exitCode)
207
207
  - **De-duplication** через `Set`: якщо monorepo-структура повертає однаковий `utils/`-шлях двічі (наприклад, `.`-root і назва вкладеного пакета перетинаються), він обробиться лише раз.
208
208
  - **Помилки `readdir`** глушаться — недоступний каталог просто пропускається без падіння всієї перевірки.
209
209
 
210
- ### Залежність від конвенцій правила `js-lint.mdc`
210
+ ### Залежність від конвенцій правила `js.mdc`
211
211
 
212
- Файл є технічною реалізацією одного з пунктів правила `js-lint.mdc`. Усі повідомлення `reporter.pass/fail` посилаються на `(js-lint.mdc)`, щоб користувач знав, де читати про сам принцип розділу `utils/` ↔ `lib/`.
212
+ Файл є технічною реалізацією одного з пунктів правила `js.mdc`. Усі повідомлення `reporter.pass/fail` посилаються на `(js.mdc)`, щоб користувач знав, де читати про сам принцип розділу `utils/` ↔ `lib/`.
@@ -141,7 +141,7 @@ export function verifyOxlintRcAgainstCanonical(cfg, canonical) {
141
141
 
142
142
  if (!deepEqualOxlintCanonical(actual, expected)) {
143
143
  failures.push(
144
- `.oxlintrc.json: поле "${key}" має збігатися з каноном пакета @nitra/cursor (npm/rules/js-lint/js/data/tooling/oxlint-canonical.json)`
144
+ `.oxlintrc.json: поле "${key}" має збігатися з каноном пакета @nitra/cursor (npm/rules/js/js/data/tooling/oxlint-canonical.json)`
145
145
  )
146
146
  }
147
147
  }
@@ -153,7 +153,7 @@ export async function check() {
153
153
  }
154
154
  }
155
155
  if (utilsDirSet.size === 0) {
156
- reporter.pass('utils-каталогів немає — перевірку пропущено (js-lint.mdc)')
156
+ reporter.pass('utils-каталогів немає — перевірку пропущено (js.mdc)')
157
157
  return reporter.getExitCode()
158
158
  }
159
159
  let violations = 0
@@ -167,7 +167,7 @@ export async function check() {
167
167
  for (const src of imports) {
168
168
  if (PARENT_RELATIVE_RE.test(src)) {
169
169
  const rel = relative(root, file)
170
- reporter.fail(`${rel}: заборонений імпорт '${src}' — utils/-файли мають бути generic (js-lint.mdc)`)
170
+ reporter.fail(`${rel}: заборонений імпорт '${src}' — utils/-файли мають бути generic (js.mdc)`)
171
171
  violations += 1
172
172
  }
173
173
  }
@@ -175,7 +175,7 @@ export async function check() {
175
175
  }
176
176
  if (violations === 0) {
177
177
  reporter.pass(
178
- `utils-каталогів: ${utilsDirSet.size}, перевірено ${checkedFiles} файлів — domain-bound імпортів немає (js-lint.mdc)`
178
+ `utils-каталогів: ${utilsDirSet.size}, перевірено ${checkedFiles} файлів — domain-bound імпортів немає (js.mdc)`
179
179
  )
180
180
  }
181
181
  return reporter.getExitCode()
package/rules/js/js.mdc CHANGED
@@ -5,7 +5,7 @@ alwaysApply: false
5
5
  version: '1.30'
6
6
  ---
7
7
 
8
- **oxlint**, **ESLint**, **jscpd**, **knip**. Запуск — **`n-cursor lint js-lint js-lint-ci`** (локально; у CI — `--read-only`, без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config`** — версія не нижче канонічного мінімуму зі snippet нижче (semver-поріг, єдине джерело істини) (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint). Dependency-політику CI-етапу (`@e18e/eslint-plugin` і oxlint/eslint/jscpd/knip окремо не додавати) винесено в `js-lint-ci`.
8
+ **oxlint**, **ESLint**, **jscpd**, **knip**. Запуск — **`n-cursor lint js`** (локально; у CI — `--read-only`, без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config`** — версія не нижче канонічного мінімуму зі snippet нижче (semver-поріг, єдине джерело істини) (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint). Dependency-політика CI-етапу: `@e18e/eslint-plugin` і oxlint/eslint/jscpd/knip окремо не додавати.
9
9
 
10
10
  У кожному **`package.json`** проєкту (корінь і всі workspace-пакети) має бути **`"type": "module"`** — весь код у ESM.
11
11
 
@@ -18,7 +18,7 @@ version: '1.30'
18
18
  }
19
19
  ```
20
20
 
21
- Канон `type` і мінімальна `@nitra/eslint-config` (semver-поріг `devDependencies`): [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json). Окремого `lint-js` скрипта немає — лінт через **`n-cursor lint js-lint js-lint-ci`** (CI — `--read-only`).
21
+ Канон `type` і мінімальна `@nitra/eslint-config` (semver-поріг `devDependencies`): [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json). Окремого `lint-js` скрипта немає — лінт через **`n-cursor lint js`** (CI — `--read-only`).
22
22
 
23
23
  ## Розширення нових файлів — `.mjs` / `.cjs`, не `.js`
24
24
 
@@ -35,7 +35,7 @@ version: '1.30'
35
35
 
36
36
  У `.vscode/extensions.json` `recommendations` мають містити `dbaeumer.vscode-eslint`, `github.vscode-github-actions`, `oxc.oxc-vscode`: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
37
37
 
38
- У корені має бути **`.oxlintrc.json`**, який **збігається з каноном** oxlint з пакета **`@nitra/cursor`**: файл **`npm/rules/js-lint/js/data/tooling/oxlint-canonical.json`** (plugins, jsPlugins з **`@e18e/eslint-plugin`**, categories, повний набір **rules** із канону — додаткові записи в **`rules`** дозволені; також **`settings`**, **`env`**, **`globals`**). Поле **`ignorePatterns`** працює як **`rules`**: канонічні патерни з **`oxlint-canonical.json`** (наразі **`**/schema.graphql`**, **`**/auto-imports.d.ts`**) мають бути присутні, додаткові локальні glob-и дозволені. Канон **`oxlint-canonical.json`** — source-of-truth, редагується напряму; у споживачі оновлюється копіюванням файлу з репозиторію пакета. Модуль **`@e18e/eslint-plugin`** не оголошуй окремо в **`package.json`** — він уже в залежностях **`@nitra/eslint-config`** (з **3.8.0**), oxlint підвантажує його з **`node_modules`**.
38
+ У корені має бути **`.oxlintrc.json`**, який **збігається з каноном** oxlint з пакета **`@nitra/cursor`**: файл **`npm/rules/js/js/data/tooling/oxlint-canonical.json`** (plugins, jsPlugins з **`@e18e/eslint-plugin`**, categories, повний набір **rules** із канону — додаткові записи в **`rules`** дозволені; також **`settings`**, **`env`**, **`globals`**). Поле **`ignorePatterns`** працює як **`rules`**: канонічні патерни з **`oxlint-canonical.json`** (наразі **`**/schema.graphql`**, **`**/auto-imports.d.ts`**) мають бути присутні, додаткові локальні glob-и дозволені. Канон **`oxlint-canonical.json`** — source-of-truth, редагується напряму; у споживачі оновлюється копіюванням файлу з репозиторію пакета. Модуль **`@e18e/eslint-plugin`** не оголошуй окремо в **`package.json`** — він уже в залежностях **`@nitra/eslint-config`** (з **3.8.0**), oxlint підвантажує його з **`node_modules`**.
39
39
 
40
40
  Мінімум для розуміння структури (реальний корінь конфігу має збігатися з каноном повністю):
41
41
 
@@ -70,9 +70,33 @@ version: '1.30'
70
70
  .claude/worktrees/
71
71
  ```
72
72
 
73
+ ## Залежнісна політика (що не додавати)
74
+
75
+ `@e18e/eslint-plugin` окремо не додавай — він уже в залежностях `@nitra/eslint-config` (з **3.8.0**), oxlint підвантажує його з `node_modules`. Пакети oxlint/eslint/jscpd/knip теж не додавай у `devDependencies` без потреби монорепо — `bunx` тягне їх ad-hoc.
76
+
73
77
  ## knip
74
78
 
75
- Залежнісний аналіз (knip — невикористані залежності/експорти, `knip.json` канон) крос-файловий, тож винесений у правило `js-lint-ci` (`lint: full`). Див. `js-lint-ci`.
79
+ Залежнісний аналіз (knip — невикористані залежності/експорти, `knip.json` канон) крос-файловий; запускається автоматично у `lint --full` разом з jscpd.
80
+
81
+ У корені проєкту має бути **`knip.json`**, який стартує з канонічного baseline з пакета `@nitra/cursor` — файл [`npm/rules/js/js/tooling/knip-canonical.json`](./js/tooling/knip-canonical.json). Він покриває типові false-positives для наших правил: `entry` зі CLI-конфігами (eslint, stylelint, oxlint, jscpd, markdownlint-cli2, `commitlint`), `project` для `**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}`, `ignore` для `**/__fixtures__/**`, `ignoreDependencies` для пакетів, посилання на які є лише в не-JS-конфігах (`@nitra/cspell-dict`, `/@cspell\/dict-.+/`, `graphql`), і `ignoreBinaries` для CLI, які канон вимагає викликати через `npx`/`bunx` і яких заборонено додавати в `devDependencies` (`actionlint`, `cspell`, `eslint`, `git-ai`, `jscpd`, `markdownlint-cli2`, `oxfmt`, `oxlint`, `shellcheck`, `uvx`, `v8r`, `zizmor`).
82
+
83
+ Якщо `knip.json` відсутній — `npx @nitra/cursor fix js` копіює канон у корінь проєкту (side effect). Після створення модифікуй файл під свій проєкт як завгодно: перевіряємо лише наявність, зміст подальших змін не валідується.
84
+
85
+ Пакет `knip` окремо в `devDependencies` не додавай — `bunx knip` тягне його ad-hoc, як oxlint/eslint/jscpd.
86
+
87
+ ## Заборона `@nitra/as-integrations-fastify`
88
+
89
+ Пакет **`@nitra/as-integrations-fastify`** заборонений у **`dependencies`**, **`peerDependencies`** та в import-specifier-ах. Це чистий републіш upstream, застряглий на peer `@apollo/server: "^4.0.0"`, тож на Apollo 5 `bun install` дає `warn: incorrect peer dependency`. Заміна — upstream **`@as-integrations/fastify`** (**`^3.1.0`**): peer `@apollo/server: "^4.0.0 || ^5.0.0"` + `fastify: "^5.3.0"`.
90
+
91
+ Міграція — лише specifier у трьох місцях: залежність у `package.json`, `import`, та `vi.mock(...)` / `await import(...)` у тестах. **Код не міняється**, експорти ті самі: `default` → `fastifyApollo`, named → `fastifyApolloDrainPlugin`.
92
+
93
+ ```javascript title="❌ до"
94
+ import fastifyApollo, { fastifyApolloDrainPlugin } from '@nitra/as-integrations-fastify'
95
+ ```
96
+
97
+ ```javascript title="✅ після"
98
+ import fastifyApollo, { fastifyApolloDrainPlugin } from '@as-integrations/fastify'
99
+ ```
76
100
 
77
101
  ## jscpd: рефакторинг і структура
78
102
 
@@ -95,7 +119,7 @@ version: '1.30'
95
119
 
96
120
  Швидкий тест: якщо файл завтра можна опублікувати окремим npm-пакетом без переписування — це `utils/`; якщо він тримає domain-state, читає конфіг проєкту або викликає зовнішні сервіси/файли — це `lib/`. Не плутай із чужими каталогами на кшталт `shared/` чи `common/`: канонічні назви — лише `utils/` і `lib/`.
97
121
 
98
- Автоматично гарантується: `npx @nitra/cursor fix js-lint` (концерн `utils_imports`) обходить кожен `utils/`-каталог у воркспейсах і падає, якщо знаходить relative-імпорт з `..` (вихід за межі каталогу) у будь-якому не-тестовому файлі. Це механічне втілення правила «utils не знає про домен»: тільки same-dir (`./X`), bare-пакети та `node:*` дозволені; cross-rule, конфіги проєкту чи sibling-utils — fail. Якщо потрібна domain-залежність — перенеси файл у `lib/`.
122
+ Автоматично гарантується: `npx @nitra/cursor fix js` (концерн `utils_imports`) обходить кожен `utils/`-каталог у воркспейсах і падає, якщо знаходить relative-імпорт з `..` (вихід за межі каталогу) у будь-якому не-тестовому файлі. Це механічне втілення правила «utils не знає про домен»: тільки same-dir (`./X`), bare-пакети та `node:*` дозволені; cross-rule, конфіги проєкту чи sibling-utils — fail. Якщо потрібна domain-залежність — перенеси файл у `lib/`.
99
123
 
100
124
  Додай workflow:
101
125
 
@@ -209,4 +233,4 @@ for (const item of arr) {
209
233
 
210
234
  ## Покриття + мутаційне тестування JS
211
235
 
212
- Покриття + мутаційне тестування JS постачаються через `n-cursor coverage` (правило `test.mdc`). Реалізація провайдера — у `npm/rules/js-lint/coverage/coverage.mjs`: `bun test --coverage --coverage-reporter=lcov` + `bunx stryker run`. Stryker конфігурується в `stryker.config.mjs` у JS-корені (single-package або `workspaces[0]`).
236
+ Покриття + мутаційне тестування JS постачаються через `n-cursor coverage` (правило `test.mdc`). Реалізація провайдера — у `npm/rules/js/coverage/coverage.mjs`: `bun test --coverage --coverage-reporter=lcov` + `bunx stryker run`. Stryker конфігурується в `stryker.config.mjs` у JS-корені (single-package або `workspaces[0]`).
package/rules/js/main.mjs CHANGED
@@ -77,6 +77,20 @@ function lintFullProject(cwd, readOnly) {
77
77
  return runInherit(readOnly ? ['eslint', '.'] : ['eslint', '--fix', '.'], cwd)
78
78
  }
79
79
 
80
+ /**
81
+ * Крос-файловий аналіз: jscpd (дублікати) + knip (невикористані залежності/експорти).
82
+ * Ігнорує `files` — завжди по всьому репо.
83
+ * @param {string} cwd корінь репо
84
+ * @returns {number} exit code
85
+ */
86
+ function lintFullCi(cwd) {
87
+ const jscpd = spawnSync('bunx', ['jscpd', '.'], { cwd, stdio: 'inherit' })
88
+ const jc = typeof jscpd.status === 'number' ? jscpd.status : 1
89
+ if (jc !== 0) return jc
90
+ const knip = spawnSync('bunx', ['knip', '--no-config-hints'], { cwd, stdio: 'inherit' })
91
+ return typeof knip.status === 'number' ? knip.status : 1
92
+ }
93
+
80
94
  /**
81
95
  * Quick-режим: авто-фікс змінених файлів, тоді класифікація лишених findings
82
96
  * на introduced / pre-existing (беклог #6/A). Блокування на будь-якому finding.
@@ -102,7 +116,7 @@ function lintChangedClassified(js, cwd, readOnly) {
102
116
  // Краш інструмента (ненульовий exit + непарсабельний json) НЕ можна тихо пропустити
103
117
  // як «чисто» — це регресія проти старого fail-fast. Фейлимо явно.
104
118
  if ((ox === null && oxRes.status !== 0) || (es === null && esRes.status !== 0)) {
105
- process.stderr.write('❌ js-lint: інструмент завершився з помилкою (не lint-порушення) — json не розпарсено\n')
119
+ process.stderr.write('❌ js: інструмент завершився з помилкою (не lint-порушення) — json не розпарсено\n')
106
120
  return 1
107
121
  }
108
122
 
@@ -110,13 +124,14 @@ function lintChangedClassified(js, cwd, readOnly) {
110
124
  if (findings.length === 0) return 0
111
125
 
112
126
  const classified = classifyFindings(findings, addedLinesByFile(js, cwd), cwd)
113
- const header = `❌ js-lint: ${findings.length} порушень (introduced ${classified.introduced.length}, pre-existing ${classified.preExisting.length})`
127
+ const header = `❌ js: ${findings.length} порушень (introduced ${classified.introduced.length}, pre-existing ${classified.preExisting.length})`
114
128
  process.stdout.write(`${header}\n${renderFindings(classified, cwd)}\n`)
115
129
  return 1
116
130
  }
117
131
 
118
132
  /**
119
- * Запускає oxlint+eslint. За замовчуванням з автофіксом; `opts.readOnly` — лише детект.
133
+ * Запускає oxlint+eslint (per-file або full) + jscpd+knip (лише full).
134
+ * За замовчуванням — з автофіксом; `opts.readOnly` — лише детект.
120
135
  * @param {string[] | undefined} files per-file: лише ці файли; undefined: весь проєкт (--full)
121
136
  * @param {string} [cwd] корінь репо
122
137
  * @param {{ readOnly?: boolean }} [opts] readOnly → без `--fix` (нуль мутацій)
@@ -125,7 +140,9 @@ function lintChangedClassified(js, cwd, readOnly) {
125
140
  export function lint(files, cwd = process.cwd(), opts = {}) {
126
141
  const readOnly = opts.readOnly === true
127
142
  if (files === undefined) {
128
- return Promise.resolve(lintFullProject(cwd, readOnly))
143
+ const esCode = lintFullProject(cwd, readOnly)
144
+ if (esCode !== 0) return Promise.resolve(esCode)
145
+ return Promise.resolve(lintFullCi(cwd))
129
146
  }
130
147
  const js = filterJsFiles(files)
131
148
  if (js.length === 0) return Promise.resolve(0)
@@ -133,6 +150,6 @@ export function lint(files, cwd = process.cwd(), opts = {}) {
133
150
  }
134
151
 
135
152
  if (isRunAsCli(import.meta.url)) {
136
- // Standalone: bun rules/js-lint/main.mjs — повний еквівалент `npx @nitra/cursor check js-lint`.
153
+ // Standalone: bun rules/js/main.mjs — повний еквівалент `npx @nitra/cursor check js`.
137
154
  process.exitCode = await runRuleCli(import.meta.dirname)
138
155
  }
@@ -1,4 +1,4 @@
1
- # Перевірка `.jscpd.json` (js-lint.mdc).
1
+ # Перевірка `.jscpd.json` (js.mdc).
2
2
  #
3
3
  # Канон надходить через --data: { "template": { "snippet": ... } }
4
4
  # Структура --data сформована з template/.jscpd.json.snippet.json.
@@ -17,7 +17,7 @@ deny contains msg if {
17
17
  not is_array(expected_value)
18
18
  actual := object.get(input, key, null)
19
19
  actual != expected_value
20
- msg := sprintf(".jscpd.json має містити \"%s\": %v (js-lint.mdc)", [key, expected_value])
20
+ msg := sprintf(".jscpd.json має містити \"%s\": %v (js.mdc)", [key, expected_value])
21
21
  }
22
22
 
23
23
  # Array subset-of для reporters.
@@ -27,7 +27,7 @@ deny contains msg if {
27
27
  actual_set := {v | some v in object.get(input, field, [])}
28
28
  some required in expected_values
29
29
  not required in actual_set
30
- msg := sprintf(".jscpd.json має містити \"%s\": [\"%s\"] (js-lint.mdc)", [field, required])
30
+ msg := sprintf(".jscpd.json має містити \"%s\": [\"%s\"] (js.mdc)", [field, required])
31
31
  }
32
32
 
33
33
  # minLines: must be number and >= expected.
@@ -35,7 +35,7 @@ deny contains msg if {
35
35
  expected := data.template.snippet.minLines
36
36
  actual := object.get(input, "minLines", null)
37
37
  not is_valid_min_lines(actual, expected)
38
- msg := sprintf(".jscpd.json має містити \"minLines\" як число >= %d (js-lint.mdc)", [expected])
38
+ msg := sprintf(".jscpd.json має містити \"minLines\" як число >= %d (js.mdc)", [expected])
39
39
  }
40
40
 
41
41
  is_valid_min_lines(actual, expected) if {
@@ -4,5 +4,5 @@
4
4
  "single": ".jscpd.json",
5
5
  "required": true
6
6
  },
7
- "missingMessage": ".jscpd.json не існує — створи з полями згідно js-lint.mdc"
7
+ "missingMessage": ".jscpd.json не існує — створи з полями згідно js.mdc"
8
8
  }
@@ -1,4 +1,4 @@
1
- # Перевірка `.github/workflows/lint-js.yml` (js-lint.mdc).
1
+ # Перевірка `.github/workflows/lint-js.yml` (js.mdc).
2
2
  #
3
3
  # Канон надходить через --data: { "template": { "snippet": ... } }
4
4
  # Структура --data сформована з template/lint-js.yml.snippet.yml.
@@ -50,7 +50,7 @@ all_run_blob := concat("\n", [r |
50
50
  deny contains msg if {
51
51
  some required_use in expected_uses_set
52
52
  not contains(all_uses_blob, required_use)
53
- msg := sprintf("lint-js.yml: відсутній крок uses: %s (js-lint.mdc)", [required_use])
53
+ msg := sprintf("lint-js.yml: відсутній крок uses: %s (js.mdc)", [required_use])
54
54
  }
55
55
 
56
56
  # ── deny: required run substrings ───────────────────────────────────────
@@ -58,26 +58,26 @@ deny contains msg if {
58
58
  deny contains msg if {
59
59
  some required_run in expected_run_substrings
60
60
  not contains(all_run_blob, required_run)
61
- msg := sprintf("lint-js.yml: у run немає %q (js-lint.mdc)", [required_run])
61
+ msg := sprintf("lint-js.yml: у run немає %q (js.mdc)", [required_run])
62
62
  }
63
63
 
64
64
  # ── deny: actions/checkout@v6 has persist-credentials: false (inverse) ──
65
65
 
66
66
  deny contains msg if {
67
67
  not has_checkout_persist_credentials_false
68
- msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
68
+ msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js.mdc)"
69
69
  }
70
70
 
71
71
  # ── deny: --fix у CI заборонено (inverse) ───────────────────────────────
72
72
 
73
73
  deny contains msg if {
74
74
  regex.match(`bunx\s+oxlint[^\n]*--fix`, all_run_blob)
75
- msg := "lint-js.yml: у run є oxlint з `--fix` (у CI заборонено) (js-lint.mdc)"
75
+ msg := "lint-js.yml: у run є oxlint з `--fix` (у CI заборонено) (js.mdc)"
76
76
  }
77
77
 
78
78
  deny contains msg if {
79
79
  contains(all_run_blob, "eslint --fix")
80
- msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js-lint.mdc)"
80
+ msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js.mdc)"
81
81
  }
82
82
 
83
83
  # ── helpers ─────────────────────────────────────────────────────────────
@@ -37,4 +37,4 @@ jobs:
37
37
  - uses: ./.github/actions/setup-bun-deps
38
38
 
39
39
  - name: Eslint
40
- run: n-cursor lint js-lint js-lint-ci --read-only
40
+ run: n-cursor lint js --read-only
@@ -1,4 +1,4 @@
1
- # Перевірка `package.json` (js-lint.mdc).
1
+ # Перевірка `package.json` (js.mdc).
2
2
  #
3
3
  # Канон надходить через --data: { "template": { "snippet": ... } }
4
4
  # Структура --data сформована з template/package.json.snippet.json
@@ -20,7 +20,7 @@ deny contains msg if {
20
20
  not is_object(expected_value)
21
21
  actual := object.get(input, key, null)
22
22
  actual != expected_value
23
- msg := sprintf("package.json: \"%s\" має бути %q (js-lint.mdc)", [key, expected_value])
23
+ msg := sprintf("package.json: \"%s\" має бути %q (js.mdc)", [key, expected_value])
24
24
  }
25
25
 
26
26
  # ── deny: scripts (nested) — exact match із normalize ──────────────────
@@ -29,7 +29,7 @@ deny contains msg if {
29
29
  some script_name, expected in data.template.snippet.scripts
30
30
  actual := object.get(object.get(input, "scripts", {}), script_name, "")
31
31
  normalize_script(actual) != expected
32
- msg := sprintf("package.json: scripts.%s має бути %q (js-lint.mdc)", [script_name, expected])
32
+ msg := sprintf("package.json: scripts.%s має бути %q (js.mdc)", [script_name, expected])
33
33
  }
34
34
 
35
35
  # ── deny: engines.node >= 24 (inverse, у rego) ──────────────────────────
@@ -37,13 +37,13 @@ deny contains msg if {
37
37
  deny contains msg if {
38
38
  engines := object.get(input, "engines", {})
39
39
  not engines_node_meets(object.get(engines, "node", ""))
40
- msg := "package.json: engines.node має бути >= 24 (js-lint.mdc)"
40
+ msg := "package.json: engines.node має бути >= 24 (js.mdc)"
41
41
  }
42
42
 
43
43
  deny contains msg if {
44
44
  engines := object.get(input, "engines", {})
45
45
  not engines_bun_meets(object.get(engines, "bun", ""))
46
- msg := "package.json: engines.bun має бути >= 1.3 (js-lint.mdc)"
46
+ msg := "package.json: engines.bun має бути >= 1.3 (js.mdc)"
47
47
  }
48
48
 
49
49
  # ── deny: @nitra/eslint-config >= snippet-поріг ─────────────────────────
@@ -51,14 +51,14 @@ deny contains msg if {
51
51
  deny contains msg if {
52
52
  dev := object.get(input, "devDependencies", {})
53
53
  not "@nitra/eslint-config" in object.keys(dev)
54
- msg := "package.json: відсутній @nitra/eslint-config у devDependencies (js-lint.mdc)"
54
+ msg := "package.json: відсутній @nitra/eslint-config у devDependencies (js.mdc)"
55
55
  }
56
56
 
57
57
  deny contains msg if {
58
58
  range := object.get(object.get(input, "devDependencies", {}), "@nitra/eslint-config", "")
59
59
  range != ""
60
60
  not eslint_config_meets_min(range)
61
- msg := sprintf("package.json: @nitra/eslint-config має бути >= %s (зараз %q) (js-lint.mdc)", [eslint_min_display, range])
61
+ msg := sprintf("package.json: @nitra/eslint-config має бути >= %s (зараз %q) (js.mdc)", [eslint_min_display, range])
62
62
  }
63
63
 
64
64
  # ── helpers ──────────────────────────────────────────────────────────────
@@ -4,5 +4,5 @@
4
4
  "single": ".vscode/extensions.json",
5
5
  "required": true
6
6
  },
7
- "missingMessage": ".vscode/extensions.json не існує — додай recommendations з js-lint.mdc"
7
+ "missingMessage": ".vscode/extensions.json не існує — додай recommendations з js.mdc"
8
8
  }
@@ -1,4 +1,4 @@
1
- # Перевірка `.vscode/extensions.json` для js-lint (js-lint.mdc).
1
+ # Перевірка `.vscode/extensions.json` для js (js.mdc).
2
2
  #
3
3
  # Канон надходить через --data: { "template": { "snippet": ... } }
4
4
  package js_lint.vscode_extensions
@@ -8,5 +8,5 @@ import rego.v1
8
8
  deny contains msg if {
9
9
  some rec in data.template.snippet.recommendations
10
10
  not rec in {r | some r in object.get(input, "recommendations", [])}
11
- msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js-lint.mdc)", [rec])
11
+ msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js.mdc)", [rec])
12
12
  }
@@ -29,7 +29,7 @@ docgen:
29
29
 
30
30
  ## Експорти / API
31
31
 
32
- Модуль експортує тільки named-exports (узгоджено з `n-js-lint` / `js-run.mdc`):
32
+ Модуль експортує тільки named-exports (узгоджено з `n-js` / `js-run.mdc`):
33
33
 
34
34
  | Експорт | Тип | Призначення |
35
35
  | -------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
@@ -6,7 +6,7 @@ import { join } from 'node:path'
6
6
  import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
7
7
 
8
8
  // Зовнішні файли конфігу stylelint, які підхоплює cosmiconfig. Канон нових
9
- // JS-конфігів — `.mjs`/`.cjs` (js-lint.mdc), legacy `.js` лишається валідним.
9
+ // JS-конфігів — `.mjs`/`.cjs` (js.mdc), legacy `.js` лишається валідним.
10
10
  const STYLELINT_CONFIG_FILES = [
11
11
  '.stylelintrc.json',
12
12
  '.stylelintrc.js',
@@ -11,7 +11,7 @@ docgen:
11
11
 
12
12
  ## Поведінка
13
13
 
14
- 1. Перевірити наявність `js-lint` у конфігурації.
14
+ 1. Перевірити наявність `js` у конфігурації.
15
15
  2. Зібрати кореневі пакети.
16
16
  3. Перевірити наявність базових конфігурацій.
17
17
  4. Перевірити наявність базлайн-файлів.
@@ -18,7 +18,7 @@ const STRYKER_VUE_PLUGIN_PATH = join(HERE, 'data', 'stryker_config', 'stryker-vu
18
18
  const STRYKER_VUE_PLUGIN_FILENAME = 'stryker-vue-macros-ignorer.mjs'
19
19
  const VITEST_BASELINE_PATH = join(HERE, 'data', 'vitest_config', 'vitest.config.baseline.js')
20
20
 
21
- // Канонічна назва vitest-конфіга — `.mjs` (нові файли, js-lint.mdc); legacy
21
+ // Канонічна назва vitest-конфіга — `.mjs` (нові файли, js.mdc); legacy
22
22
  // `.js` лишається валідним. Перший знайдений виграє (.mjs пріоритетніший).
23
23
  const VITEST_CONFIG_NAMES = ['vitest.config.mjs', 'vitest.config.js']
24
24
  // Заміна literal `configFile` у скопійованому stryker-baseline на фактичне
@@ -332,14 +332,14 @@ export async function check(cwd = process.cwd()) {
332
332
  const reporter = createCheckReporter()
333
333
  const config = await readNCursorConfigLite(cwd)
334
334
 
335
- // Self-gate: js-lint має бути enabled
336
- if (!config.rules.includes('js-lint') || config.disableRules.includes('js-lint')) {
335
+ // Self-gate: js має бути enabled
336
+ if (!config.rules.includes('js') || config.disableRules.includes('js')) {
337
337
  return reporter.getExitCode()
338
338
  }
339
339
 
340
340
  const jsRoots = await resolveAllJsRoots(cwd)
341
341
  if (jsRoots.length === 0) {
342
- reporter.fail('test: js-lint enabled, але кореневий package.json не знайдено (test.mdc)')
342
+ reporter.fail('test: js enabled, але кореневий package.json не знайдено (test.mdc)')
343
343
  return reporter.getExitCode()
344
344
  }
345
345
 
@@ -8,7 +8,7 @@ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
8
8
  /** Subтring-pattern: `pool: 'forks'` або `pool: "forks"` (з опційним whitespace). */
9
9
  const POOL_FORKS_RE = /pool\s*:\s*['"]forks['"]/u
10
10
 
11
- // Канонічна назва — `.mjs` (нові файли, js-lint.mdc), але legacy `.js` лишається
11
+ // Канонічна назва — `.mjs` (нові файли, js.mdc), але legacy `.js` лишається
12
12
  // валідним. Перший знайдений виграє: `.mjs` пріоритетніший.
13
13
  const VITEST_CONFIG_NAMES = ['vitest.config.mjs', 'vitest.config.js']
14
14
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: JS-тести (*.test.mjs) живуть у tests/. Правило `test` керує stryker.config.mjs + vitest.config.mjs (якщо js-lint enabled) і .cargo/mutants.toml (якщо rust enabled).
2
+ description: JS-тести (*.test.mjs) живуть у tests/. Правило `test` керує stryker.config.mjs + vitest.config.mjs (якщо js enabled) і .cargo/mutants.toml (якщо rust enabled).
3
3
  version: '2.8'
4
4
  globs: "**/{.n-cursor.json,package.json,Cargo.toml,stryker.config.mjs,vitest.config.mjs,vitest.config.js,.cargo/mutants.toml},**/*.test.mjs"
5
5
  alwaysApply: false
@@ -132,7 +132,7 @@ test.skipIf(env.STRYKER_MUTATOR_WORKER)('узгоджені з поточним
132
132
 
133
133
  **Scoped-режим `--changed`:** `n-cursor coverage --changed` звужує scope до файлів, змінених від git merge-base поточної гілки з `main` або `origin/main`; якщо обидва refs відсутні — до робочого дерева vs HEAD. `git diff <base>` проти робочого дерева ловить committed і uncommitted однаково, тож результат не залежить від того, чи крок уже закомічено. Недосяжний `base` (rebase/force-update) — fail-closed (помилка, не тихий pass). JS-провайдер ганяє `vitest --changed <base>` (лише зачеплені тести) і Stryker `--mutate` по змінених production-файлах (тест-файли відкидаються); roots без змінених JS пропускаються. Rust-провайдер пропускається, якщо не змінено `.rs`/`Cargo.*` (інакше — повний crate-прогін; per-file scoping cargo-mutants — окремий крок). Порожній scope (нема релевантних змін) — pass. У changed-режимі `COVERAGE.md` **не** перезаписується (рішення гейту — лише exit-код) і LLM-класифікація не запускається. Швидкі gate-и викликають саме `coverage --changed`; повний coverage (увесь проєкт, запис `COVERAGE.md`) лишається для `bun run coverage` / `/n-coverage-fix`.
134
134
 
135
- Провайдери живуть у `npm/rules/<rule>/coverage/coverage.mjs` (постачаються правилами мови/рантайму: `js-lint`, `rust`, у майбутньому `python` тощо). Оркестратор — у `npm/rules/test/coverage/coverage.mjs`.
135
+ Провайдери живуть у `npm/rules/<rule>/coverage/coverage.mjs` (постачаються правилами мови/рантайму: `js`, `rust`, у майбутньому `python` тощо). Оркестратор — у `npm/rules/test/coverage/coverage.mjs`.
136
136
 
137
137
  У `package.json` (корінь) має бути `scripts.coverage` із викликом `n-cursor coverage`:
138
138
 
@@ -144,7 +144,7 @@ test.skipIf(env.STRYKER_MUTATOR_WORKER)('узгоджені з поточним
144
144
 
145
145
  ## Налаштування mutation-testing
146
146
 
147
- Якщо у `.n-cursor.json#rules` присутнє правило `js-lint` — правило `test` створює canonical baseline `stryker.config.mjs` + `vitest.config.mjs` у **кожному** JS-root проєкту: у кожному workspace з власним `package.json` (або в корені для single-package). У monorepo з `workspaces: ['app', 'scripts']` отримаєте `app/stryker.config.mjs` + `app/vitest.config.mjs` і `scripts/stryker.config.mjs` + `scripts/vitest.config.mjs`. Якщо у JS-root уже лежить legacy `vitest.config.js` — він лишається валідним, новий `.mjs` поряд не створюється, а `vitest.configFile` у скопійованому `stryker.config.mjs` приводиться до фактичного імені.
147
+ Якщо у `.n-cursor.json#rules` присутнє правило `js` — правило `test` створює canonical baseline `stryker.config.mjs` + `vitest.config.mjs` у **кожному** JS-root проєкту: у кожному workspace з власним `package.json` (або в корені для single-package). У monorepo з `workspaces: ['app', 'scripts']` отримаєте `app/stryker.config.mjs` + `app/vitest.config.mjs` і `scripts/stryker.config.mjs` + `scripts/vitest.config.mjs`. Якщо у JS-root уже лежить legacy `vitest.config.js` — він лишається валідним, новий `.mjs` поряд не створюється, а `vitest.configFile` у скопійованому `stryker.config.mjs` приводиться до фактичного імені.
148
148
 
149
149
  Канон Stryker config (Vitest runner + perTest): [stryker.config.baseline.mjs](./js/data/stryker_config/stryker.config.baseline.mjs)
150
150
 
@@ -202,7 +202,7 @@ Customization (mutate patterns, exclude rules, timeout) — відповідал
202
202
 
203
203
  Якщо інше правило спеціалізує mutation-behavior — воно зобов'язане **доповнювати** існуючий `.cargo/mutants.toml` без дублювання (додавати лише відсутні ключі) і **не перетирати** ручні налаштування. Послідовний запуск `npx @nitra/cursor fix test` після `fix tauri` не має скидати tauri-tuning, і навпаки — повторний `fix tauri` не дублює секції.
204
204
 
205
- Додатково: коли `js-lint` enabled, концерн `stryker_config` без дублювання додає у кореневий `.gitignore` тест-патерни:
205
+ Додатково: коли `js` enabled, концерн `stryker_config` без дублювання додає у кореневий `.gitignore` тест-патерни:
206
206
 
207
207
  - `**/reports/stryker/` — увесь каталог Stryker-output-у (backup'и `tempDirName`, `mutation.json`, HTML/dashboard-репорти якщо додасте інші reporter-и).
208
208
  - `**/coverage/` — весь output vitest v8 coverage (`lcov.info` + HTML `lcov-report/`). Ефемерний: регенерується кожним прогоном, фінальні метрики живуть у `COVERAGE.md`. Gitignore не заважає `n-cursor coverage` читати `lcov.info` у тому ж прогоні.
@@ -145,8 +145,8 @@ for (const rule of rules) {
145
145
  ```javascript
146
146
  import { discoverOneRule } from './discover-checkable-rules.mjs'
147
147
 
148
- const rule = await discoverOneRule('/abs/path/to/npm/rules/n-js-lint', 'n-js-lint')
149
- // rule.id === 'n-js-lint'
148
+ const rule = await discoverOneRule('/abs/path/to/npm/rules/n-js', 'n-js')
149
+ // rule.id === 'n-js'
150
150
  // rule.jsConcerns — список JS-концернів у js/
151
151
  // rule.policyConcerns — список policy-концернів у policy/
152
152
  ```
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Допоміжні функції для аналізу GitHub Actions workflow (`.yml`) після структурного розбору YAML.
3
3
  *
4
- * Використовується в check-ga, check-js-lint, check-text, check-style-lint, check-npm-module замість
4
+ * Використовується в check-ga, check-js, check-text, check-style-lint, check-npm-module замість
5
5
  * пошуку підрядків у сирому тексті там, де важливі лише значення `uses:` та `run:` кроків.
6
6
  *
7
7
  * Для `run:` також виявляється shell-продовження рядка через `\\` перед переносом (антипатерн у ga.mdc).
@@ -41,7 +41,7 @@ export function formatDurationMs(ms) {
41
41
  * ```
42
42
  *
43
43
  * Ширина колонки id вирівнюється під найдовший id у списку. Мінімальна ширина risk — 14
44
- * (узгоджено з типовою довжиною заголовків `fix-js-lint` / `lint-security`).
44
+ * (узгоджено з типовою довжиною заголовків `fix-js` / `lint-security`).
45
45
  * @param {string} title заголовок таблиці (наприклад, `Fix timing` або `Lint timing`)
46
46
  * @param {TimingEntry[]} timings записи в порядку запуску — друкуються як є, не сортуються
47
47
  * @returns {string} готовий до stdout текст з кінцевим `\n`
@@ -2,7 +2,7 @@
2
2
  * Копіює composite GitHub Action `setup-bun-deps` з установленого пакету `@nitra/cursor`
3
3
  * у цільовий репозиторій (`.github/actions/setup-bun-deps/action.yml`).
4
4
  *
5
- * Використовується CLI `npx \@nitra/cursor`, щоб workflows з правил `ga` / `js-lint` / `text`
5
+ * Використовується CLI `npx \@nitra/cursor`, щоб workflows з правил `ga` / `js` / `text`
6
6
  * могли одразу викликати `uses: ./.github/actions/setup-bun-deps` після кроку `actions/checkout@v6` (без checkout runner не знайде action.yml).
7
7
  *
8
8
  * Джерело: каталог `github-actions/setup-bun-deps/` у корені tarball пакету (поруч із `mdc/`, `bin/`).
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Резолвить корінь JS-коду в проєкті: для workspace-projects — перший workspace
3
3
  * (з підтримкою glob-патернів типу `cf/*`), для single-package — корінь cwd.
4
- * Спільна утиліта для coverage-провайдера js-lint і test-концерну stryker_config (DRY).
4
+ * Спільна утиліта для coverage-провайдера js і test-концерну stryker_config (DRY).
5
5
  */
6
6
  import { existsSync } from 'node:fs'
7
7
  import { glob, readFile } from 'node:fs/promises'
@@ -1 +1 @@
1
- { "auto": ["js-lint"], "worktree": true }
1
+ { "auto": ["js"], "worktree": true }
@@ -52,7 +52,7 @@ bun run lint
52
52
  | **oxlint / ESLint** | `.oxlintrc.json` → `ignorePatterns`; `eslint.config.js` → `ignores`; `eslint-disable` / `oxlint-disable` у коді |
53
53
  | **інше** | `.v8rignore`, `.stylelintignore`, `.trufflehog-exclude`, розширення `ignores` у workflow-конфігах |
54
54
 
55
- Політика узгоджена з **`.cursor/rules/`** (зокрема **n-js-lint**, **n-text**): виняток допустимий лише з **обґрунтованою** причиною, не як заміна рефакторингу для справжніх клонів / дублікатів.
55
+ Політика узгоджена з **`.cursor/rules/`** (зокрема **n-js**, **n-text**): виняток допустимий лише з **обґрунтованою** причиною, не як заміна рефакторингу для справжніх клонів / дублікатів.
56
56
 
57
57
  ### Коли обовʼязково питати користувача
58
58
 
@@ -109,7 +109,7 @@ bun run lint
109
109
  - **oxlint**: **`--threads=1`**, якщо потрібно зменшити навантаження на CPU.
110
110
  - **ESLint cache**: **`--cache`** / **`--cache-location .eslintcache`** — менше повторного читання з диска.
111
111
 
112
- Канонічний рядок **`lint-js`** у репозиторіях з **`check js-lint`** фіксований; додаткові прапорці — з узгодженням канону або в споживацькому проєкті окремо.
112
+ Канонічний рядок **`lint-js`** у репозиторіях з **`check js`** фіксований; додаткові прапорці — з узгодженням канону або в споживацькому проєкті окремо.
113
113
 
114
114
  ## Примітка
115
115
 
@@ -242,7 +242,7 @@ description: >-
242
242
 
243
243
  # Обмеження
244
244
 
245
- - Дотриматись `.cursor/rules/n-js-lint.mdc` і `.cursor/rules/n-changelog.mdc`
245
+ - Дотриматись `.cursor/rules/n-js.mdc` і `.cursor/rules/n-changelog.mdc`
246
246
  (зміни у workspace = change-файл, не ручний CHANGELOG/version bump).
247
247
  - Якщо `eslint ^9` офіційно не підтримує Node 25 — підняти peer range.
248
248
 
@@ -1,28 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: fix.mjs
4
- resource: npm/rules/js-lint-ci/fix.mjs
5
- docgen:
6
- crc: 38cf876b
7
- score: 100
8
- ---
9
-
10
- Файл застосовує визначене правило до вхідного контексту прогону. Застосовує правило та повертає отриманий результат.
11
-
12
- ## Поведінка
13
-
14
- 1. Запуск правила.
15
- - Приймає контекст прогону.
16
- - Виконує правило.
17
- - Повертає результат.
18
-
19
- ## Публічний API
20
-
21
- run — запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
22
- Library mode — викликається CLI orchestration через `import + run`.
23
-
24
- ## Гарантії поведінки
25
-
26
- - Read-only: файл не виконує операцій запису у файлову систему.
27
- - Кешує результати в межах одного прогону.
28
- - Не звертається до мережі.
@@ -1,12 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/js-lint-ci
4
- resource: npm/rules/js-lint-ci/
5
- ---
6
-
7
- # npm/rules/js-lint-ci
8
-
9
- | Файл | Тип |
10
- | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
- | [main.mjs](main.md) | JS Module |
@@ -1,27 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: main.mjs
4
- resource: npm/rules/js-lint-ci/main.mjs
5
- docgen:
6
- crc: 59cd2fd3
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- ---
10
-
11
- ## Огляд
12
-
13
- Модуль надає інструменти для глибокого аналізу коду. Функція `run` виконує перевірку коду відповідно до заданого контексту прогону. Функція `lint` порівнює файли репозиторію для виявлення дублікатів та неіспользумого коду.
14
-
15
- ## Поведінка
16
-
17
- run виконує перевірку на основі контексту прогону.
18
- lint виконує крос-файловий аналіз репозиторію на дублікати та мертвий код.
19
-
20
- ## Публічний API
21
-
22
- run — точка входу для виконання правила, що включає перевірку логіки застосування (JS-занепокложення $\to$ політика $\to$ посилання MDC) та аналіз коду (jscpd + knip) по всьому репозиторію.
23
- lint — аналіз коду по всьому репозиторію на наявність дублікатів (jscpd) та мертвого коду (knip).
24
-
25
- ## Гарантії поведінки
26
-
27
- - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,11 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/js-lint-ci/js
4
- resource: npm/rules/js-lint-ci/js/
5
- ---
6
-
7
- # npm/rules/js-lint-ci/js
8
-
9
- | Файл | Тип |
10
- | ------------------- | --------- |
11
- | [lint.mjs](lint.md) | JS Module |
@@ -1,45 +0,0 @@
1
- ---
2
- description: Крос-файловий ci-етап js-lint — jscpd (детектор клонів) і knip (невикористані експорти). Лише у `lint --full`, по всьому репо.
3
- globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"
4
- alwaysApply: false
5
- version: '1.0'
6
- ---
7
-
8
- # js-lint-ci — крос-файловий ci-етап
9
-
10
- `jscpd` і `knip` аналізують увесь граф проєкту, тож мають сенс лише у повному прогоні
11
- `npx @nitra/cursor lint --full` (CI: `lint --read-only --full`) — не у швидкому `lint` по змінених файлах. Per-file режиму нема.
12
-
13
- Швидкий етап js-lint (oxlint/eslint) — у правилі `js-lint` (`lint: per-file`).
14
-
15
- ## Залежнісна політика (що не додавати)
16
-
17
- Залежнісний аналіз — крос-файлова зона цього ci-етапу, тож і політика «що не додавати в залежності» живе тут.
18
-
19
- `@e18e/eslint-plugin` окремо не додавай — він уже в залежностях `@nitra/eslint-config` (з **3.8.0**), oxlint підвантажує його з `node_modules`. Пакети oxlint/eslint/jscpd/knip теж не додавай у `devDependencies` без потреби монорепо — `bunx` тягне їх ad-hoc.
20
-
21
- ## knip
22
-
23
- Перевірку невикористаних залежностей і експортів виконує **knip** (заміна `depcheck`). Викликається у скрипті `lint-js` і в CI разом з oxlint/eslint/jscpd — окремий крок у CI не потрібен.
24
-
25
- У корені проєкту має бути **`knip.json`**, який стартує з канонічного baseline з пакета `@nitra/cursor` — файл [`npm/rules/js-lint/js/tooling/knip-canonical.json`](../js-lint/js/tooling/knip-canonical.json). Він покриває типові false-positives для наших правил: `entry` зі CLI-конфігами (eslint, stylelint, oxlint, jscpd, markdownlint-cli2, `commitlint`), `project` для `**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}`, `ignore` для `**/__fixtures__/**`, `ignoreDependencies` для пакетів, посилання на які є лише в не-JS-конфігах (`@nitra/cspell-dict`, `/@cspell\/dict-.+/`, `graphql`), і `ignoreBinaries` для CLI, які канон вимагає викликати через `npx`/`bunx` і яких заборонено додавати в `devDependencies` (`actionlint`, `cspell`, `eslint`, `git-ai`, `jscpd`, `markdownlint-cli2`, `oxfmt`, `oxlint`, `shellcheck`, `uvx`, `v8r`, `zizmor`).
26
-
27
- Якщо `knip.json` відсутній — `npx @nitra/cursor fix js-lint` копіює канон у корінь проєкту (side effect). Після створення модифікуй файл під свій проєкт як завгодно: перевіряємо лише наявність, зміст подальших змін не валідується.
28
-
29
- Пакет `knip` окремо в `devDependencies` не додавай — `bunx knip` тягне його ad-hoc, як oxlint/eslint/jscpd.
30
-
31
- ## Заборона `@nitra/as-integrations-fastify`
32
-
33
- Пакет **`@nitra/as-integrations-fastify`** заборонений у **`dependencies`**, **`peerDependencies`** та в import-specifier-ах. Це чистий републіш upstream, застряглий на peer `@apollo/server: "^4.0.0"`, тож на Apollo 5 `bun install` дає `warn: incorrect peer dependency`. Заміна — upstream **`@as-integrations/fastify`** (**`^3.1.0`**): peer `@apollo/server: "^4.0.0 || ^5.0.0"` + `fastify: "^5.3.0"`.
34
-
35
- Як і knip, це крос-файлова dependency-політика ci-етапу, а не per-file перевірка — інваріант «per-file режиму нема» зберігається.
36
-
37
- Міграція — лише specifier у трьох місцях: залежність у `package.json`, `import`, та `vi.mock(...)` / `await import(...)` у тестах. **Код не міняється**, експорти ті самі: `default` → `fastifyApollo`, named → `fastifyApolloDrainPlugin`.
38
-
39
- ```javascript title="❌ до"
40
- import fastifyApollo, { fastifyApolloDrainPlugin } from '@nitra/as-integrations-fastify'
41
- ```
42
-
43
- ```javascript title="✅ після"
44
- import fastifyApollo, { fastifyApolloDrainPlugin } from '@as-integrations/fastify'
45
- ```
@@ -1,33 +0,0 @@
1
- import { spawnSync } from 'node:child_process'
2
-
3
- import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
4
- import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
5
-
6
- /**
7
- * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня (applies → JS-concerns
8
- * → policy → mdc-refs); `lint()` — lint-поверхня (jscpd + knip, крос-файловий аналіз).
9
- * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону
10
- * @returns {Promise<number>} 0 — OK, 1 — порушення
11
- */
12
- export function run(ctx) {
13
- return runStandardRule(import.meta.dirname, ctx)
14
- }
15
-
16
- /**
17
- * lint-поверхня: jscpd (дублікати) + knip (мертвий код) по всьому репо.
18
- * @param {string[] | undefined} _files ігнорується (крос-файловий аналіз)
19
- * @param {string} [cwd] корінь репо
20
- * @returns {Promise<number>} 0 — OK, ≠0 — порушення
21
- */
22
- export function lint(_files, cwd = process.cwd()) {
23
- const jscpd = spawnSync('bunx', ['jscpd', '.'], { cwd, stdio: 'inherit' })
24
- const jc = typeof jscpd.status === 'number' ? jscpd.status : 1
25
- if (jc !== 0) return Promise.resolve(jc)
26
- const knip = spawnSync('bunx', ['knip', '--no-config-hints'], { cwd, stdio: 'inherit' })
27
- return Promise.resolve(typeof knip.status === 'number' ? knip.status : 1)
28
- }
29
-
30
- if (isRunAsCli(import.meta.url)) {
31
- // Standalone: bun rules/<id>/main.mjs — повний еквівалент `npx @nitra/cursor check <id>`.
32
- process.exitCode = await runRuleCli(import.meta.dirname)
33
- }
@@ -1 +0,0 @@
1
- { "auto": { "glob": ["**/*.mjs", "**/*.cjs", "**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] }, "lint": "full" }