@nitra/cursor 12.11.3 → 12.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.12.0] - 2026-06-25
4
+
5
+ ### Changed
6
+
7
+ - Додано автоматичне додавання посилань на template-файли у main.mdc
8
+ - Додано підтримку Blue Oak Bronze+ для перевірки ліцензій у `bun`, `python` та `rust
9
+
3
10
  ## [12.11.3] - 2026-06-25
4
11
 
5
12
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -1452,11 +1452,11 @@ async function runSync() {
1452
1452
 
1453
1453
  /**
1454
1454
  * Команди, що мутують проєкт у CWD і вимагають кореня репо. `undefined`/`''` —
1455
- * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only `trace`,
1455
+ * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only,
1456
1456
  * `--root`-команди `doc-aggregate`/`rename-yaml-extensions`,
1457
1457
  * sub-лінтери) гард не зачіпає.
1458
1458
  */
1459
- const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'lint', 'change', 'release'])
1459
+ const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'lint', 'release'])
1460
1460
 
1461
1461
  /**
1462
1462
  * Короткий опис дії для тексту root-guard помилки за іменем команди.
@@ -1472,9 +1472,6 @@ function describeRootGuardedAction(cmd) {
1472
1472
  case 'lint': {
1473
1473
  return '`lint` за замовчуванням авто-fix лінтерів (oxfmt/eslint --fix/stylelint --fix) і конформності (--full) у поточному каталозі'
1474
1474
  }
1475
- case 'change': {
1476
- return '`change` пише change-файл у .changes/ поточного каталогу'
1477
- }
1478
1475
  case 'release': {
1479
1476
  return '`release` бампає version і переписує CHANGELOG у поточному каталозі'
1480
1477
  }
@@ -1489,10 +1486,10 @@ const [command, ...args] = process.argv.slice(2)
1489
1486
 
1490
1487
  try {
1491
1488
  // Root-guard до перших мутацій: дефолтний sync скаффолдить .cursor/.claude/CLAUDE.md/
1492
- // .n-cursor.json + bun install, а lint/change/release переписують файли в CWD —
1489
+ // .n-cursor.json + bun install, а lint/release переписують файли в CWD —
1493
1490
  // усе це ключиться на cwd(). Запуск із піддиректорії git-репо (типово прямий
1494
1491
  // `bun npm/bin/n-cursor.js` не з кореня) зачепив би не той каталог → STOP. Read-only та
1495
- // `--root`-команди (trace, graph, doc-aggregate, rename-yaml-extensions) не зачіпаємо.
1492
+ // `--root`-команди (doc-aggregate, rename-yaml-extensions) не зачіпаємо.
1496
1493
  if (ROOT_GUARDED_COMMANDS.has(command)) {
1497
1494
  assertCwdIsProjectRoot(cwd(), describeRootGuardedAction(command))
1498
1495
  }
@@ -1558,12 +1555,6 @@ try {
1558
1555
 
1559
1556
  break
1560
1557
  }
1561
- case 'change': {
1562
- const { runChangeCli } = await import('../rules/release/change.mjs')
1563
- process.exitCode = await runChangeCli(args)
1564
-
1565
- break
1566
- }
1567
1558
  case 'release': {
1568
1559
  const { runReleaseCli } = await import('../rules/release/release.mjs')
1569
1560
  process.exitCode = await runReleaseCli(args)
@@ -1575,14 +1566,6 @@ try {
1575
1566
 
1576
1567
  break
1577
1568
  }
1578
- case 'trace': {
1579
- // n-cursor trace — наскрізна простежуваність (spec §5.4/§7): граф
1580
- // ADR↔spec↔plan↔change за front-matter + флаг розривів. exit 1 на розрив.
1581
- const { runTraceCli } = await import('../scripts/dispatcher/trace.mjs')
1582
- process.exitCode = runTraceCli(args)
1583
-
1584
- break
1585
- }
1586
1569
  case 'adr-normalize-local': {
1587
1570
  // Local-backend ADR-нормалізації: викликається з .claude/hooks/normalize-decisions.sh
1588
1571
  // як заміна single-shot LLM-виклику. Проганяє конвеєр (retrieval→edge-judge→
@@ -1601,7 +1584,7 @@ try {
1601
1584
  default: {
1602
1585
  console.error(`❌ Невідома команда: ${command}`)
1603
1586
  console.error(
1604
- ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), analyze-escalation, taze, start-check, release, skill, trace, doc-aggregate`
1587
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), analyze-escalation, taze, start-check, release, skill, doc-aggregate`
1605
1588
  )
1606
1589
  process.exitCode = 1
1607
1590
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.11.3",
3
+ "version": "12.12.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,26 +3,25 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/bun/main.mjs
5
5
  docgen:
6
- crc: a2cfc572
6
+ crc: 18950415
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль виконує перевірку відповідності коду встановленим політикам, використовуючи маркер повідомлень (bun.mdc). Він також аналізує ліцензії залежностей, спираючись на конфігурацію .licensee.json. Модуль надає функції для запуску перевірок (run) та лінтингу (lint).
13
+ Модуль застосовує політики до коду та перевіряє ліцензії залежностей (bun.mdc). Функція `run` застосовує політику до коду, використовуючи кешування у межах прогону для прискорення. Функція `lint` перевіряє ліцензії npm-залежностей, спираючись на конфігурацію в .licensee.json. У режимі `fix` вона створює .licensee.json, а в режимі `readOnly` блокує виконання.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- run виконує перевірку коду, застосовуючи політики та посилання MDC.
18
- lint виконує перевірку ліцензій npm-залежностей, якщо існує файл .licensee.json у корені проєкту.
17
+ run виконує перевірку, застосовуючи політику до коду, використовуючи кешування.
18
+ lint виконує перевірку ліцензій npm-залежностей, генеруючи `.licensee.json` у fix-режимі або відмовляючись у readOnly режимі.
19
19
 
20
20
  ## Публічний API
21
21
 
22
- run — Основний вхідний пункт правила, який виконує перевірку: застосовує логіку, перевіряє відповідність політикам та згадки в (bun.mdc).
23
- lint — Інструмент для перевірки ліцензій npm-залежностей у всьому репозиторії, що активується через конфігурацію (.licensee.json).
22
+ run — Точка входу правила, що виконує перевірку: застосовує логіку, перевіряє відповідність політиці та посилання на маркери повідомлень (bun.mdc).
23
+ lint — Інструмент для перевірки ліцензій npm-залежностей у всьому репозиторії. У режимі `--full` він працює повноцінно; у режимі виправлення автоматично створює файл .licensee.json, якщо його немає.
24
24
 
25
25
  ## Гарантії поведінки
26
26
 
27
- - Read-only: не виконує операцій запису (ФС/БД).
28
27
  - Кешує результати в межах одного прогону.
@@ -1 +1 @@
1
- { "auto": { "glob": "package.json" }, "lint": "full" }
1
+ { "auto": { "glob": ["package.json", ".licensee.json"] }, "lint": "full" }
@@ -5,4 +5,6 @@ alwaysApply: false
5
5
  version: '2.1'
6
6
  ---
7
7
 
8
- Проект використовує тільки Bun для керування залежностями та запуску скриптів.
8
+ Проект використовує тільки Bun для керування залежностями та запуску скриптів.
9
+ - [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
10
+ - [bunfig.toml.snippet.toml](./policy/bunfig/template/bunfig.toml.snippet.toml)
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'node:fs'
1
+ import { existsSync, writeFileSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { spawnSync } from 'node:child_process'
4
4
 
@@ -19,20 +19,30 @@ export function run(ctx) {
19
19
  return runStandardRule(import.meta.dirname, ctx)
20
20
  }
21
21
 
22
+ /** Дефолтний allowlist: Blue Oak bronze — дозволяє MIT/Apache/BSD/ISC, блокує GPL/AGPL/LGPL. */
23
+ const DEFAULT_LICENSEE_CONFIG = JSON.stringify({ licenses: { blueOak: 'bronze' }, corrections: true }, null, 2) + '\n'
24
+
22
25
  /**
23
- * Перевірка ліцензій npm-залежностей через `licensee`. Opt-in: пропускається якщо
24
- * `.licensee.json` відсутній у cwd (проєкт не налаштував allowlist). `bun x licensee`
25
- * не потребує локальної установки bunx завантажує пакет ad-hoc.
26
+ * Перевірка ліцензій npm-залежностей через `licensee`.
27
+ * У fix-режимі: якщо `.licensee.json` відсутній генерує його з дефолтним allowlist
28
+ * (blueOak: bronze) і запускає перевірку. У readOnly (CI): відсутність файлу → fail.
26
29
  * @param {string} [cwd] корінь проєкту
30
+ * @param {{ readOnly?: boolean }} [opts]
27
31
  * @returns {number} 0 — OK, 1 — порушення
28
32
  */
29
- function runLicenseeSteps(cwd = process.cwd()) {
33
+ function runLicenseeSteps(cwd = process.cwd(), opts = {}) {
34
+ const readOnly = opts.readOnly === true
30
35
  const reporter = createCheckReporter()
31
36
  const { pass, fail } = reporter
32
37
 
33
- if (!existsSync(join(cwd, '.licensee.json'))) {
34
- pass('lint-bun: licensee — немає .licensee.json, перевірку ліцензій пропущено')
35
- return reporter.getExitCode()
38
+ const configPath = join(cwd, '.licensee.json')
39
+ if (!existsSync(configPath)) {
40
+ if (readOnly) {
41
+ fail('lint-bun: licensee — немає .licensee.json; запустіть `npx @nitra/cursor fix bun` локально для генерації (bun.mdc)')
42
+ return reporter.getExitCode()
43
+ }
44
+ writeFileSync(configPath, DEFAULT_LICENSEE_CONFIG, 'utf8')
45
+ pass('lint-bun: licensee — створено .licensee.json з дефолтним allowlist (blueOak: bronze)')
36
46
  }
37
47
 
38
48
  const bun = resolveCmd('bun')
@@ -53,14 +63,14 @@ function runLicenseeSteps(cwd = process.cwd()) {
53
63
 
54
64
  /**
55
65
  * Оркестраторний адаптер `n-cursor lint bun`: licensee-перевірка ліцензій npm-залежностей.
56
- * Whole-repo (ігнорує `_files`). Opt-in через `.licensee.json` у cwd.
66
+ * Whole-repo (ігнорує `_files`). Fix-режим: auto-генерує `.licensee.json` якщо відсутній.
57
67
  * @param {string[] | undefined} _files ігнорується
58
68
  * @param {string} [cwd] корінь
59
- * @param {{ readOnly?: boolean }} [_opts] не використовується (licensee завжди read-only)
69
+ * @param {{ readOnly?: boolean }} [opts] readOnly → не мутує ФС; відсутність конфігу → fail
60
70
  * @returns {Promise<number>} exit code
61
71
  */
62
- export function lint(_files, cwd = process.cwd(), _opts = {}) {
63
- return runStandardLint(import.meta.dirname, () => runLicenseeSteps(cwd))
72
+ export function lint(_files, cwd = process.cwd(), opts = {}) {
73
+ return runStandardLint(import.meta.dirname, () => runLicenseeSteps(cwd, opts))
64
74
  }
65
75
 
66
76
  if (isRunAsCli(import.meta.url)) {
@@ -372,3 +372,4 @@ Rego-перевірки, що запускаються через `conftest` у
372
372
  | --------------------------- | -------------------------------------------------------------------- |
373
373
  | `ci4.vscode_extensions` | `.vscode/extensions.json` містить `arr.marksman` у `recommendations` |
374
374
 
375
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
package/rules/ga/main.mdc CHANGED
@@ -5,4 +5,12 @@ globs: ".github/workflows/*.yml"
5
5
  alwaysApply: false
6
6
  ---
7
7
 
8
- Правило **ga** перевіряє структуру `.github/workflows/`, наявність обов'язкових workflow-файлів і їх відповідність канонам, а також налаштування VS Code та zizmor для роботи з GitHub Actions.
8
+ Правило **ga** перевіряє структуру `.github/workflows/`, наявність обов'язкових workflow-файлів і їх відповідність канонам, а також налаштування VS Code та zizmor для роботи з GitHub Actions.
9
+ - [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
10
+ - [git-ai.yml.snippet.yml](./policy/git_ai/template/git-ai.yml.snippet.yml)
11
+ - [clean-ga-workflows.yml.snippet.yml](./policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml)
12
+ - [lint-ga.yml.snippet.yml](./policy/lint_ga/template/lint-ga.yml.snippet.yml)
13
+ - [zizmor.yml.snippet.yml](./policy/zizmor_yml/template/zizmor.yml.snippet.yml)
14
+ - [clean-merged-branch.yml.snippet.yml](./policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml)
15
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
16
+ - [uses-min-versions.snippet.json](./policy/workflow_common/template/uses-min-versions.snippet.json)
package/rules/js/main.mdc CHANGED
@@ -11,3 +11,7 @@ version: '1.30'
11
11
 
12
12
  Rego-пакети у `policy/` — запускаються `npx @nitra/cursor fix js` або `conftest`:
13
13
 
14
+ - [lint-js.yml.snippet.yml](./policy/lint_js_yml/template/lint-js.yml.snippet.yml)
15
+ - [.jscpd.json.snippet.json](./policy/jscpd/template/.jscpd.json.snippet.json)
16
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
17
+ - [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
@@ -11,3 +11,6 @@ version: '1.12'
11
11
 
12
12
  Rego-пакети, які запускає `npx @nitra/cursor fix js-run` / `npx @nitra/cursor check`:
13
13
 
14
+ - [jsconfig.json.snippet.json](./policy/jsconfig/template/jsconfig.json.snippet.json)
15
+ - [configmap.yaml.contains.yml](./policy/configmap/template/configmap.yaml.contains.yml)
16
+ - [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
@@ -18,3 +18,7 @@ Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.g
18
18
  Rego-пакети (запускаються через `npx @nitra/cursor fix`):
19
19
 
20
20
  - `npm_module.npm_publish_yml` — template-driven перевірка `.github/workflows/npm-publish.yml` (deep-subset: усі обовʼязкові поля й кроки з канонічного сніпету).
21
+ - [package.json.snippet.json](./policy/npm_package_json/template/package.json.snippet.json)
22
+ - [npm-publish.yml.snippet.yml](./policy/npm_publish_yml/template/npm-publish.yml.snippet.yml)
23
+ - [package.json.snippet.json](./policy/root_package_json/template/package.json.snippet.json)
24
+ - [tsconfig.emit-types.json.snippet.json](./policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json)
@@ -3,29 +3,30 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/python/main.mjs
5
5
  docgen:
6
- crc: 7021a379
6
+ crc: b072766d
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Запускає `lint-python` за правилом `python.mdc` на базі [uv](https://docs.astral.sh/uv/). Якщо `pyproject.toml` відсутній у корені, вихід дорівнює 0 без запуску інструментів. Якщо `pyproject.toml` присутній, але `uv` не знайдено в PATH, це помилка (використовується лише `uv`, без Poetry). Обов'язково виконує `uv lock --check` для перевірки актуальності lock-файлу та `uv sync --frozen` для збірки середовища. Опціональні лінтери (`ruff`, `mypy`) запускаються лише якщо доступні через `uv run`. `ruff` виконується в режимі автоматичного виправлення (`ruff check --fix .`, `ruff format .`) та перевірки коду (`mypy .`). Це відповідає канону патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`).
13
+ Виконує послідовність кроків для валідації коду Python відповідно до правил, визначених у (python.mdc). Перед запуском інструментів перевіряє наявність `pyproject.toml` у корені; якщо він відсутній, завершує роботу з кодом 0. Якщо файл присутній, перевіряє наявність `uv` у PATH, оскільки він є єдиним пакет-менеджером. Обов'язково виконує `uv lock --check` для підтвердження актуальності lock-файлу та `uv sync --frozen` для синхронізації середовища з `uv.lock` (див. https://docs.astral.sh/uv/). Опційні лінтери запускаються лише за умови їх доступності через `uv run`. При використанні `ruff` застосовується автоматичне виправлення (`auto-fix`), що модифікує робоче дерево. Усі операції виконуються з механізмом перехоплення помилок (fail-safe), запобігаючи викиданню винятків назовні.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- run виконує стандартну перевірку для правила.
18
- runLintPythonSteps виконує послідовність кроків лінтування Python, включаючи перевірку `pyproject.toml`, виконання `uv lock --check` та `uv sync --frozen`, а також запуск опціональних лінтерів (`ruff`, `mypy`, `liccheck`) через `uv run` за умови їх доступності.
19
- runLintPython запускає послідовність кроків лінтування Python, використовуючи механізм стандартного лінтування.
20
- lint оркеструє запуск `runLintPython` для виконання лінтування Python.
17
+ run виконує стандартну перевірку на основі контексту.
18
+ runLintPythonSteps виконує повний цикл лінтування Python, включаючи перевірку `uv lock --check`, `uv sync --frozen` та запуск лінтерів, якщо `pyproject.toml` присутній.
19
+ runLintPython запускає кроки лінтування Python, використовуючи механізм серіалізації через `runStandardLint`.
20
+ lint оркеструє запуск `runLintPython` з опцією `readOnly` для детектних перевірок.
21
21
 
22
22
  ## Публічний API
23
23
 
24
- run — Основна точка входу для виконання правил, яка перевіряє аспекти застосування (JS-занепокложення $\rightarrow$ політика $\rightarrow$ mdc-посилання) та виконує лінтинг.
25
- runLintPythonSteps — Виконує внутрішні етапи лінтингу Python без збереження логів.
26
- runLintPython — Публічний інтерфейс командного рядка для запуску лінтингу Python, який синхронізується з станом Git-дерева.
27
- lint — Координатор, що викликає публічний інтерфейс для лінтингу Python.
24
+ run — Точка входу для виконання правил, яка перевіряє аспекти застосування (JS-занепокложення $\rightarrow$ політика $\rightarrow$ mdc-посилання).
25
+ runLintPythonSteps — Виконує внутрішні етапи перевірки коду Python без збереження результатів.
26
+ runLintPython — Публічний інтерфейс командного рядка для запуску перевірки коду Python, що забезпечує унікальність завдяки блоку блокування та аналізу стану Git-дерева.
27
+ lint — Адаптер, що керує запуском перевірки коду Python, делегуючи це `runLintPython`.
28
28
 
29
29
  ## Гарантії поведінки
30
30
 
31
31
  - Read-only: не виконує операцій запису (ФС/БД).
32
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -30,6 +30,7 @@ import { resolveCmd } from '../../scripts/utils/resolve-cmd.mjs'
30
30
  import { runStandardLint } from '../../scripts/lib/run-standard-lint.mjs'
31
31
  import { runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
32
32
  import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
33
+ import { getBronzeAndAbove, isSpdxAllowed } from '../../scripts/lib/blue-oak.mjs'
33
34
 
34
35
  /**
35
36
  * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня (applies → JS-concerns
@@ -112,13 +113,54 @@ export function runLintPythonSteps(cwd = process.cwd(), opts = {}) {
112
113
  return runTool(label, uv, ['run', '--frozen', tool, ...args], pass, fail)
113
114
  }
114
115
 
116
+ /**
117
+ * Перевірка ліцензій Python-залежностей через pip-licenses + Blue Oak Bronze+.
118
+ * Opt-in: пропускається якщо pip-licenses не встановлений у uv-середовищі.
119
+ * @param {string} uvPath абсолютний шлях до uv
120
+ * @param {string} cwdPath корінь проєкту
121
+ * @param {(msg: string) => void} passF
122
+ * @param {(msg: string) => void} failF
123
+ * @returns {boolean} true якщо OK або пропущено; false якщо порушення
124
+ */
125
+ function checkPipLicenses(uvPath, cwdPath, passF, failF) {
126
+ if (!uvToolAvailable(uvPath, 'pip-licenses')) {
127
+ passF('lint-python: pip-licenses недоступний у uv-середовищі — перевірку ліцензій пропущено')
128
+ return true
129
+ }
130
+ const r = spawnSync(uvPath, ['run', '--frozen', 'pip-licenses', '--from=mixed', '--format=spdx-json'], {
131
+ cwd: cwdPath, stdio: ['ignore', 'pipe', 'inherit'], shell: false,
132
+ })
133
+ if (r.status !== 0) {
134
+ failF('lint-python: pip-licenses — помилка виконання')
135
+ return false
136
+ }
137
+ const allowed = getBronzeAndAbove()
138
+ let doc
139
+ try { doc = JSON.parse(r.stdout.toString('utf8')) } catch { doc = null }
140
+ const packages = doc?.packages ?? []
141
+ const violations = packages.filter(pkg => {
142
+ const lic = pkg.licenseDeclared ?? pkg.licenseConcluded ?? 'NOASSERTION'
143
+ return !isSpdxAllowed(lic, allowed)
144
+ })
145
+ if (violations.length > 0) {
146
+ for (const pkg of violations) {
147
+ const lic = pkg.licenseDeclared ?? pkg.licenseConcluded ?? 'NOASSERTION'
148
+ process.stdout.write(` ✗ ${pkg.name}@${pkg.versionInfo ?? '?'}: ${lic}\n`)
149
+ }
150
+ failF(`lint-python: pip-licenses — ${violations.length} пакет(ів) поза Blue Oak Bronze+ (python.mdc)`)
151
+ return false
152
+ }
153
+ passF(`lint-python: pip-licenses — ліцензії OK (Blue Oak Bronze+, ${packages.length} пакетів)`)
154
+ return true
155
+ }
156
+
115
157
  const ruffCheck = readOnly ? ['check', '.'] : ['check', '--fix', '.']
116
158
  const ruffFormat = readOnly ? ['format', '--check', '.'] : ['format', '.']
117
159
  if (!runOptionalUvTool('ruff', readOnly ? 'ruff check' : 'ruff check --fix', ruffCheck)) return reporter.getExitCode()
118
160
  if (!runOptionalUvTool('ruff', readOnly ? 'ruff format --check' : 'ruff format', ruffFormat))
119
161
  return reporter.getExitCode()
120
162
  if (!runOptionalUvTool('mypy', 'mypy', ['.'])) return reporter.getExitCode()
121
- if (!runOptionalUvTool('liccheck', 'liccheck', [])) return reporter.getExitCode()
163
+ if (!checkPipLicenses(uv, cwd, pass, fail)) return reporter.getExitCode()
122
164
 
123
165
  return reporter.getExitCode()
124
166
  }
@@ -16,3 +16,5 @@ alwaysApply: false
16
16
  | `rego.vscode_extensions` | `.vscode/extensions.json` | `recommendations` містить `tsandall.opa` |
17
17
  | `rego.vscode_settings` | `.vscode/settings.json` | `[rego]`-блок з `defaultFormatter` + `formatOnSave` |
18
18
 
19
+ - [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
20
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
@@ -3,25 +3,26 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/rust/main.mjs
5
5
  docgen:
6
- crc: cbe9e650
6
+ crc: bbddef51
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль забезпечує виконання перевірки коду Rust на відповідність встановленим правилам. Він також надає функціонал для запуску повного процесу лінтингу, що включає форматування та аналіз коду.
13
+ Модуль надає інструменти для забезпечення якості коду на Rust. Він дозволяє виконати перевірку стилю за допомогою функції `lint` або запустити повну оркестрацію, що включає форматування та аналіз, за допомогою функції `run`. (rust.mdc)
14
14
 
15
15
  ## Поведінка
16
16
 
17
- run виконує перевірку коду Rust, застосовуючи політику, визначену в документації.
18
- lint запускає повний процес лінтингу Rust, включаючи форматування та аналіз, і повертає код виходу.
17
+ run виконує стандартну перевірку для Rust-коду.
18
+
19
+ lint запускає оркестрацію перевірки Rust-коду, включаючи форматування та аналіз.
19
20
 
20
21
  ## Публічний API
21
22
 
22
- run — точка входу для виконання правила, що перевіряє логіку, пов'язану з JS-зацікавленостями, політикою та посиланнями MDC.
23
- lint — точка входу для запуску лінтера Rust-коду.
23
+ run — виконує основну перевірку, що включає перевірку логіки, пов'язаної з JS, політики та посиланнями MDC (rust.mdc).
24
+ lint — забезпечує фіксацію та виконання перевірок стилю коду (аналогічно cargo fmt/clippy) для `n-cursor lint rust`.
24
25
 
25
26
  ## Гарантії поведінки
26
27
 
27
- - Read-only: не виконує операцій запису (ФС/БД).
28
+ - (специфічних машинно-виведених гарантій немає)
@@ -1 +1 @@
1
- { "auto": { "glob": "**/Cargo.toml" }, "lint": "full" }
1
+ { "auto": { "glob": ["**/Cargo.toml", "deny.toml"] }, "lint": "full" }
@@ -1,6 +1,6 @@
1
1
  /** @see ./docs/lint.md */
2
2
  import { spawnSync } from 'node:child_process'
3
- import { existsSync } from 'node:fs'
3
+ import { existsSync, writeFileSync } from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
 
6
6
  import { createCheckReporter } from '../../scripts/lib/check-reporter.mjs'
@@ -8,6 +8,7 @@ import { runStandardLint } from '../../scripts/lib/run-standard-lint.mjs'
8
8
  import { resolveCmd } from '../../scripts/utils/resolve-cmd.mjs'
9
9
  import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
10
10
  import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
11
+ import { generateDenyTomlLicenses } from '../../scripts/lib/blue-oak.mjs'
11
12
 
12
13
  /**
13
14
  * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня (applies → JS-concerns
@@ -83,8 +84,18 @@ function runRustLint(cwd = process.cwd(), opts = {}) {
83
84
  fail
84
85
  )
85
86
 
86
- // cargo deny check licenses (opt-in): потрібен deny.toml + cargo-deny у PATH
87
- if (existsSync(join(cwd, 'deny.toml'))) {
87
+ // cargo deny check licenses: fix-режим — auto-генерує deny.toml якщо відсутній;
88
+ // readOnly (CI) — відсутність файлу → fail.
89
+ const denyConfigPath = join(cwd, 'deny.toml')
90
+ if (!existsSync(denyConfigPath)) {
91
+ if (readOnly) {
92
+ fail('lint-rust: cargo deny — немає deny.toml; запустіть `npx @nitra/cursor fix rust` локально для генерації (rust.mdc)')
93
+ } else {
94
+ writeFileSync(denyConfigPath, generateDenyTomlLicenses(), 'utf8')
95
+ pass('lint-rust: cargo deny — створено deny.toml з дефолтним allowlist')
96
+ }
97
+ }
98
+ if (existsSync(denyConfigPath)) {
88
99
  const hasDeny = spawnSync(cargo, ['deny', '--version'], { stdio: 'ignore', shell: false }).status === 0
89
100
  if (hasDeny) {
90
101
  runCargo('cargo deny check licenses', cargo, ['deny', 'check', 'licenses'], pass, fail)
@@ -9,3 +9,5 @@ version: '2.1'
9
9
  ## Перевірка
10
10
 
11
11
  `npx @nitra/cursor fix security`
12
+ - [lint-security.yml.snippet.yml](./policy/lint_security_yml/template/lint-security.yml.snippet.yml)
13
+ - [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
@@ -5,4 +5,8 @@ globs: "**/*.{css,scss,vue}"
5
5
  alwaysApply: false
6
6
  ---
7
7
 
8
- Правило **style** для Vue-проєктів: Quasar як стильова система, SCSS-конвенції кольорів і відступів, фікси компонентів, stylelint через `@nitra/stylelint-config`.
8
+ Правило **style** для Vue-проєктів: Quasar як стильова система, SCSS-конвенції кольорів і відступів, фікси компонентів, stylelint через `@nitra/stylelint-config`.
9
+ - [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
10
+ - [lint-style.yml.snippet.yml](./policy/lint_style_yml/template/lint-style.yml.snippet.yml)
11
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
12
+ - [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
@@ -19,4 +19,5 @@ alwaysApply: false
19
19
 
20
20
  ### Multi-workspace iteration
21
21
 
22
- У monorepo `n-cursor coverage` ітерує усі workspaces з власним `package.json` і агрегує метрики lcov + Stryker у єдиний `JS`-рядок `COVERAGE.md`. Workspace без тестів пропускається без помилки.
22
+ У monorepo `n-cursor coverage` ітерує усі workspaces з власним `package.json` і агрегує метрики lcov + Stryker у єдиний `JS`-рядок `COVERAGE.md`. Workspace без тестів пропускається без помилки.
23
+ - [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
@@ -10,3 +10,12 @@ version: '1.30'
10
10
 
11
11
  Rego-пакети, що перевіряються через conftest (auto-discovered за `target.json` поряд із `.rego`):
12
12
 
13
+ - [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
14
+ - [.oxfmtrc.json.snippet.json](./policy/oxfmtrc/template/.oxfmtrc.json.snippet.json)
15
+ - [.markdownlint-cli2.jsonc.snippet.jsonc](./policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc)
16
+ - [.cspell.json.snippet.json](./policy/cspell/template/.cspell.json.snippet.json)
17
+ - [.cspell.json.deny.json](./policy/cspell/template/.cspell.json.deny.json)
18
+ - [.cspell.json.contains.json](./policy/cspell/template/.cspell.json.contains.json)
19
+ - [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
20
+ - [lint-text.yml.snippet.yml](./policy/lint_text/template/lint-text.yml.snippet.yml)
21
+ - [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
@@ -6,17 +6,18 @@ resource: npm/scripts/
6
6
 
7
7
  # npm/scripts
8
8
 
9
- | Файл | Тип |
10
- | ----------------------------------------------------------------------------------- | --------- |
11
- | [auto-rules.mjs](auto-rules.md) | JS Module |
12
- | [auto-skills.mjs](auto-skills.md) | JS Module |
13
- | [build-agents-commands.mjs](build-agents-commands.md) | JS Module |
14
- | [cli-entry.mjs](cli-entry.md) | JS Module |
9
+ | Файл | Тип |
10
+ |---|---|
11
+ | [auto-rules.mjs](auto-rules.md) | JS Module |
12
+ | [auto-skills.mjs](auto-skills.md) | JS Module |
13
+ | [build-agents-commands.mjs](build-agents-commands.md) | JS Module |
14
+ | [cli-entry.mjs](cli-entry.md) | JS Module |
15
15
  | [ensure-nitra-cursor-dev-dependencies.mjs](ensure-nitra-cursor-dev-dependencies.md) | JS Module |
16
- | [hook.mjs](hook.md) | JS Module |
17
- | [post-tool-use-check.mjs](post-tool-use-check.md) | JS Module |
18
- | [rename-yaml-extensions.mjs](rename-yaml-extensions.md) | JS Module |
19
- | [skills-cli.mjs](skills-cli.md) | JS Module |
20
- | [sync-claude-config.mjs](sync-claude-config.md) | JS Module |
21
- | [sync-setup-bun-deps-action.mjs](sync-setup-bun-deps-action.md) | JS Module |
22
- | [upgrade-nitra-cursor-and-install.mjs](upgrade-nitra-cursor-and-install.md) | JS Module |
16
+ | [hook.mjs](hook.md) | JS Module |
17
+ | [post-tool-use-check.mjs](post-tool-use-check.md) | JS Module |
18
+ | [rename-yaml-extensions.mjs](rename-yaml-extensions.md) | JS Module |
19
+ | [skills-cli.mjs](skills-cli.md) | JS Module |
20
+ | [sync-claude-config.mjs](sync-claude-config.md) | JS Module |
21
+ | [sync-setup-bun-deps-action.mjs](sync-setup-bun-deps-action.md) | JS Module |
22
+ | [update-blue-oak.mjs](update-blue-oak.md) | JS Module |
23
+ | [upgrade-nitra-cursor-and-install.mjs](upgrade-nitra-cursor-and-install.md) | JS Module |
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: JS Module
3
+ title: update-blue-oak.mjs
4
+ resource: npm/scripts/update-blue-oak.mjs
5
+ docgen:
6
+ crc: f818f73b
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 95
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Оновлює вбудований список ліцензій Blue Oak Council, отримуючи дані з https://blueoakcouncil.org/list.json. Витягує SPDX-ідентифікатори ліцензій рівнів Model, Gold, Silver та Bronze і зберігає їх у npm/data/blue-oak.json. Цей процес запускається вручну у середовищі @nitra/cursor командою bun npm/scripts/update-blue-oak.mjs. Оновлення відбувається рідко, але нові permissive ліцензії з'являються раз на кілька місяців. Lead-рівень (найгірший, GPL-compatible) навмисно виключений.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Завантажує дані з https://blueoakcouncil.org/list.json.
18
+ 2. Перевіряє успішність отримання даних. У разі невдачі зупиняє виконання.
19
+ 3. Парсить отриманий JSON-відповідь.
20
+ 4. Ітерує по рейтингах у даних.
21
+ 5. Для кожного рейтингу перевіряє, чи належить він до рівнів Model, Gold, Silver або Bronze.
22
+ 6. Якщо рейтинг відповідає критеріям, витягує всі SPDX-ідентифікатори ліцензій, пов'язані з цим рейтингом, та додає їх до списку.
23
+ 7. Формує об'єкт, що містить версію даних та зібраний список SPDX-ідентифікаторів.
24
+ 8. Зберігає цей об'єкт у файл blue-oak.json у каталозі data.
25
+
26
+ ## Гарантії поведінки
27
+
28
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Читає вбудований Blue Oak Council snapshot (`npm/data/blue-oak.json`).
3
+ * Повертає множину SPDX-ідентифікаторів рівнів Model+Gold+Silver+Bronze.
4
+ */
5
+ import { readFileSync } from 'node:fs'
6
+ import { dirname, join } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+
9
+ const DATA_PATH = join(dirname(dirname(dirname(fileURLToPath(import.meta.url)))), 'data', 'blue-oak.json')
10
+
11
+ /**
12
+ * Множина SPDX-ідентифікаторів Blue Oak Bronze і вище (Model+Gold+Silver+Bronze).
13
+ * Ліцензії з цього списку вважаються permissive-safe для комерційного проєкту.
14
+ * @returns {Set<string>}
15
+ */
16
+ export function getBronzeAndAbove() {
17
+ const { bronzeAndAbove } = JSON.parse(readFileSync(DATA_PATH, 'utf8'))
18
+ return new Set(bronzeAndAbove)
19
+ }
20
+
21
+ /**
22
+ * Генерує TOML-рядок `[licenses]` для `deny.toml` (cargo-deny) на основі Blue Oak Bronze+.
23
+ * @returns {string}
24
+ */
25
+ export function generateDenyTomlLicenses() {
26
+ const ids = [...getBronzeAndAbove()].toSorted()
27
+ const lines = ids.map(id => ` "${id}",`).join('\n')
28
+ return `[licenses]\nallow = [\n${lines}\n]\n`
29
+ }
30
+
31
+ /**
32
+ * Перевіряє SPDX-вираз проти Blue Oak Bronze+ allowlist.
33
+ * Підтримує: одиночний ID, `A OR B` (будь-який дозволений = OK), `A AND B` (усі мають бути дозволені).
34
+ * `NOASSERTION` і `NONE` завжди → false.
35
+ * @param {string} expression SPDX-вираз з pip-licenses або іншого інструмента
36
+ * @param {Set<string>} allowed множина дозволених SPDX-ідентифікаторів
37
+ * @returns {boolean}
38
+ */
39
+ export function isSpdxAllowed(expression, allowed) {
40
+ if (!expression || expression === 'NOASSERTION' || expression === 'NONE') return false
41
+ const clean = s => s.trim().replace(/^\(|\)$/g, '')
42
+ if (expression.includes(' AND ')) return expression.split(' AND ').every(p => allowed.has(clean(p)))
43
+ if (expression.includes(' OR ')) return expression.split(' OR ').some(p => allowed.has(clean(p)))
44
+ return allowed.has(clean(expression))
45
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: blue-oak.mjs
4
+ resource: npm/scripts/lib/blue-oak.mjs
5
+ docgen:
6
+ crc: 9039e57f
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль читає конфігурацію Blue Oak Council (`blue-oak.json`) для визначення дозволених рівнів SPDX. Він повертає множину SPDX-ідентифікаторів рівнів Model, Gold, Silver та Bronze. Модуль також генерує правила заборони ліцензій у форматі TOML та перевіряє відповідність будь-якого SPDX-ідентифікатора цьому списку.
14
+
15
+ ## Поведінка
16
+
17
+ getBronzeAndAbove повертає множину SPDX-ідентифікаторів рівнів Model+Gold+Silver+Bronze, які беруться з конфігурації blue-oak.json.
18
+ generateDenyTomlLicenses генерує TOML-рядок для `deny.toml` (cargo-deny), дозволяючи лише SPDX-ідентифікатори, що знаходяться у множині Blue Oak Bronze+.
19
+ isSpdxAllowed перевіряє, чи відповідає наданий SPDX-вираз дозволеним ідентифікаторам, враховуючи логічні оператори `AND` та `OR`.
20
+
21
+ ## Публічний API
22
+
23
+ getBronzeAndAbove — Визначає набір SPDX-ідентифікаторів (Model, Gold, Silver, Bronze), які вважаються безпечними для комерційного використання.
24
+ generateDenyTomlLicenses — Створює конфігураційний рядок TOML для файлу `deny.toml`, що забороняє використання ліцензій нижче рівня Blue Oak Bronze.
25
+ isSpdxAllowed — Визначає, чи відповідає заданий SPDX-ідентифікатор дозволеному списку Blue Oak Bronze+ згідно з логікою (одиночний ID, `A OR B`, `A AND B`).
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -9,8 +9,8 @@ resource: npm/scripts/lib/
9
9
  | Файл | Тип |
10
10
  |---|---|
11
11
  | [assert-project-root.mjs](assert-project-root.md) | JS Module |
12
+ | [blue-oak.mjs](blue-oak.md) | JS Module |
12
13
  | [changed-files.mjs](changed-files.md) | JS Module |
13
- | [check-mdc-template-refs.mjs](check-mdc-template-refs.md) | JS Module |
14
14
  | [check-reporter.mjs](check-reporter.md) | JS Module |
15
15
  | [diff-added-lines.mjs](diff-added-lines.md) | JS Module |
16
16
  | [discover-check-rules-from-cursor.mjs](discover-check-rules-from-cursor.md) | JS Module |
@@ -3,23 +3,25 @@ type: JS Module
3
3
  title: run-rule.mjs
4
4
  resource: npm/scripts/lib/run-rule.mjs
5
5
  docgen:
6
- crc: c9b164c7
6
+ crc: 500c35f6
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Файл оркеструє виконання одного правила під CLI `fix`. Він послідовно застосовує `applies`-гейт, а потім виконує JS-концерни та Policy-концерни. Резолвер ділить кеш між концернами, а кожен concern має власний механізм звітності, що об'єднується в єдиний exit-код правила. Процес спирається на конфігураційні файли, зокрема `target.json` та `.n-cursor.json`.
11
+ ## Огляд
12
+
13
+ Оркестратор виконує логіку одного правила під CLI `fix`. Він послідовно застосовує фільтр застосовності з `js/applies.mjs`. Далі виконуються JS-концерни для перевірки, а потім запускаються Policy-концерни, що зчитують конфігурації з `target.json` та `.n-cursor.json`. Резолвер ділить кеш між концернами, а їхні коди виходу об'єднуються в єдиний результат правила.
12
14
 
13
15
  ## Поведінка
14
16
 
15
- runTemplateSubsetConcern виконує перевірку концерну, де канон визначено у `target.json` як `template`, звіряючи вміст файлів-таргетів з шаблоном, визначеним у відповідному каталозі.
17
+ runTemplateSubsetConcern виконує перевірку концерну, де канон (сніпет) береться з `template.json` та звіряється з вмістом актуальних файлів-таргетів, використовуючи логіку subset-of.
16
18
 
17
- runRule оркеструє виконання одного правила, послідовно застосовуючи `applies`-гейт, виконуючи JS-концерни, запускаючи policy-концерни та перевіряючи відсутність markdown-посилань у `main.mdc`.
19
+ runRule оркеструє виконання одного правила, послідовно застосовуючи applies-гейт, а потім виконуючи JS-концерни та policy-концерни, збираючи загальний exit-код.
18
20
 
19
21
  ## Публічний API
20
22
 
21
- runTemplateSubsetConcern — Порівнює фактичний файл з канонічним шаблоном (`target.json:"check":"template"`), визначаючи, чи всі обов'язкові елементи з шаблону присутні у файлі.
22
- runRule — Виконує окреме правило, перевіряючи його відповідність через гейт, JavaScript-концерни та політики.
23
+ runTemplateSubsetConcern — Порівнює фактичний файл з шаблоном, визначеним у `target.json`, перевіряючи, чи всі обов'язкові елементи з шаблону присутні у файлі.
24
+ runRule — Виконує окреме правило, яке проходить через етапи застосування, перевірки на JS-концерни та політики.
23
25
 
24
26
  ## Гарантії поведінки
25
27
 
@@ -3,25 +3,25 @@ type: JS Module
3
3
  title: t0.mjs
4
4
  resource: npm/scripts/lib/fix/t0.mjs
5
5
  docgen:
6
- crc: 92c9348d
6
+ crc: 4cee4f45
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль забезпечує механізм автоматичного застосування паттернів T0-auto. Він дозволяє відфільтровувати правила, до яких застосовуються ці паттерни, та запускає повний цикл перевірки конформності з автоматичним виправленням через `runT0AutoCli`.
13
+ Модуль забезпечує автоматичне застосування паттернів T0-auto до виявлених порушень. Він фільтрує доступні правила за допомогою `filterT0AutoRules`, застосовує паттерни до порушень за допомогою `applyT0Auto` та запускає повний процес перевірки та виведення результатів через `runT0AutoCli`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- applyT0Auto застосовує всі T0-auto паттерни до одного виявленого порушення, повертаючи результат застосування та список виконаних дій.
18
- filterT0AutoRules повертає список ID правил, для яких існують відповідні T0-auto паттерни, виходячи з виявлених порушень.
19
- runT0AutoCli запускає процес T0-auto: виконує перевірку конформності, застосовує T0-auto до порушень, повторно перевіряє змінені правила та виводить підсумок.
17
+ applyT0Auto застосовує всі T0-auto паттерни до одного виявленого порушення, повертаючи результат застосування та список дій.
18
+ filterT0AutoRules повертає список ID правил, для яких існує хоча б один T0-auto паттерн, виходячи з виявлених порушень.
19
+ runT0AutoCli виконує повний цикл T0-auto: запускає перевірку конформності, застосовує T0-auto до провальних правил, повторно перевіряє змінені правила та виводить підсумок.
20
20
 
21
21
  ## Публічний API
22
22
 
23
- applyT0Auto — застосовує всі T0-auto шаблони до вихідних даних про порушення.
24
- filterT0AutoRules — визначає і повертає ідентифікатори правил, які мають принаймні один T0-auto шаблон, виходячи з результату `fix --json`.
23
+ applyT0Auto — застосовує всі T0-auto шаблони до результату виявлення порушень.
24
+ filterT0AutoRules — визначає і повертає ідентифікатори правил, які мають відповідні T0-auto шаблони у результаті `fix --json`.
25
25
  runT0AutoCli — виконує команду `n-cursor fix-t0 [rule...]`, яка запускає `fix --json`, застосовує T0-auto до кожного порушення, повторно перевіряє check-gate та виводить звіт.
26
26
 
27
27
  ## Гарантії поведінки
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url'
5
5
  import { discoverT0Patterns } from './discover-t0-patterns.mjs'
6
6
  import { runConformanceCheck } from './run-conformance-check.mjs'
7
7
 
8
- // Паттерни живуть у rule-level fix-*.mjs файлах; агрегуємо тут через discovery.
9
8
  // Top-level await: ініціалізація один раз при завантаженні модуля.
10
9
  const RULES_DIR = join(dirname(fileURLToPath(import.meta.url)), '../../../rules')
11
10
  const PATTERNS = await discoverT0Patterns(RULES_DIR)
@@ -24,7 +23,7 @@ export async function applyT0Auto(ruleId, violationOutput, cwd) {
24
23
  for (const p of PATTERNS) {
25
24
  if (!p.test(violationOutput)) continue
26
25
  // Патерн може бути sync ({ok,action}) або async (Promise) — await нормалізує обидва.
27
- const result = await p.apply(violationOutput, cwd)
26
+ const result = await p.apply(violationOutput, cwd, { ruleId, rulesDir: RULES_DIR })
28
27
  actions.push(`[${p.id}] ${result.action}`)
29
28
  if (result.ok) applied = true
30
29
  }
@@ -15,7 +15,6 @@
15
15
  import { readFile } from 'node:fs/promises'
16
16
  import { join, relative } from 'node:path'
17
17
 
18
- import { findMissingMdcRefs } from './check-mdc-template-refs.mjs'
19
18
  import { createCheckReporter } from './check-reporter.mjs'
20
19
  import { resolveTargetFiles } from './resolve-target-files.mjs'
21
20
  import { runConftestBatch } from './run-conftest-batch.mjs'
@@ -180,15 +179,5 @@ export async function runRule(rule, bundledRulesDir, walkCache) {
180
179
  if (code !== 0) totalCode = 1
181
180
  }
182
181
 
183
- const ruleDir = join(bundledRulesDir, rule.id)
184
- const missing = await findMissingMdcRefs(ruleDir)
185
- if (missing.length > 0) {
186
- const reporter = createCheckReporter()
187
- for (const rel of missing) {
188
- reporter.fail(`main.mdc: відсутнє markdown-посилання на template-файл ${rel}`)
189
- }
190
- if (reporter.getExitCode() !== 0) totalCode = 1
191
- }
192
-
193
182
  return totalCode
194
183
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Оновлює вбудований snapshot Blue Oak Council license list.
3
+ *
4
+ * Fetching: https://blueoakcouncil.org/list.json
5
+ * Виводить: npm/data/blue-oak.json — SPDX-ідентифікатори рівнів Model+Gold+Silver+Bronze.
6
+ *
7
+ * Запуск (вручну, у @nitra/cursor):
8
+ * bun npm/scripts/update-blue-oak.mjs
9
+ *
10
+ * Коли запускати:
11
+ * - При апгрейді @nitra/cursor (n-taze або вручну) — Blue Oak список змінюється рідко,
12
+ * але нові permissive ліцензії зʼявляються раз на кілька місяців;
13
+ * - Якщо проєкт падає на license-check через ліцензію якої нема в списку,
14
+ * а вона точно permissive — спочатку перевір, чи вона в Bronze+ на blueoakcouncil.org.
15
+ *
16
+ * Lead-рівень (найгірший, GPL-compatible) — навмисно виключений.
17
+ */
18
+ import { writeFileSync } from 'node:fs'
19
+ import { dirname, join } from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const BLUE_OAK_URL = 'https://blueoakcouncil.org/list.json'
23
+ const OUT_PATH = join(dirname(dirname(fileURLToPath(import.meta.url))), 'data', 'blue-oak.json')
24
+ const KEEP_RATINGS = new Set(['Model', 'Gold', 'Silver', 'Bronze'])
25
+
26
+ console.log(`⬇ Fetching ${BLUE_OAK_URL} …`)
27
+ const res = await fetch(BLUE_OAK_URL)
28
+ if (!res.ok) {
29
+ console.error(`✗ HTTP ${res.status}`)
30
+ process.exit(1)
31
+ }
32
+
33
+ /** @type {{ version: string, ratings: Array<{ name: string, licenses: Array<{ id: string, name: string, url: string }> }> }} */
34
+ const data = await res.json()
35
+
36
+ const bronzeAndAbove = []
37
+ for (const rating of data.ratings) {
38
+ if (KEEP_RATINGS.has(rating.name)) {
39
+ bronzeAndAbove.push(...rating.licenses.map(l => l.id))
40
+ }
41
+ }
42
+
43
+ const out = {
44
+ version: data.version,
45
+ source: BLUE_OAK_URL,
46
+ bronzeAndAbove,
47
+ }
48
+
49
+ writeFileSync(OUT_PATH, JSON.stringify(out, null, 2) + '\n', 'utf8')
50
+ console.log(`✓ ${OUT_PATH}`)
51
+ console.log(` version=${out.version} bronzeAndAbove=${bronzeAndAbove.length} licenses`)
@@ -1,57 +0,0 @@
1
- /**
2
- * Returns list of template/ files that are NOT referenced in <id>.mdc as
3
- * markdown link targets. Paths returned are relative to ruleDir.
4
- */
5
- import { existsSync } from 'node:fs'
6
- import { readdir, readFile, stat } from 'node:fs/promises'
7
- import { join, relative } from 'node:path'
8
-
9
- /**
10
- * @param {string} ruleDir абсолютний шлях до каталогу правила
11
- * @returns {Promise<string[]>} абсолютні шляхи всіх файлів у template/
12
- */
13
- async function walkTemplateDirs(ruleDir) {
14
- const out = []
15
- for (const kind of ['fix', 'policy']) {
16
- const kindDir = join(ruleDir, kind)
17
- if (!existsSync(kindDir)) continue
18
- for (const concern of await readdir(kindDir)) {
19
- const tpl = join(kindDir, concern, 'template')
20
- if (!existsSync(tpl)) continue
21
- const tplStat = await stat(tpl)
22
- if (!tplStat.isDirectory()) continue
23
- out.push(...(await collectFiles(tpl)))
24
- }
25
- }
26
- return out.map(p => relative(ruleDir, p))
27
- }
28
-
29
- /**
30
- * @param {string} dir каталог для обходу
31
- * @returns {Promise<string[]>} абсолютні шляхи знайдених файлів
32
- */
33
- async function collectFiles(dir) {
34
- const out = []
35
- for (const entry of await readdir(dir, { withFileTypes: true })) {
36
- const full = join(dir, entry.name)
37
- if (entry.isDirectory()) out.push(...(await collectFiles(full)))
38
- else out.push(full)
39
- }
40
- return out
41
- }
42
-
43
- /**
44
- * @param {string} ruleDir абсолютний шлях до npm/rules/<id>/
45
- * @param {string} ruleId basename правила (напр. "security")
46
- * @returns {Promise<string[]>} відносні шляхи template-файлів без посилань у .mdc
47
- */
48
- export async function findMissingMdcRefs(ruleDir) {
49
- const mdcPath = join(ruleDir, 'main.mdc')
50
- if (!existsSync(mdcPath)) return []
51
- const mdc = await readFile(mdcPath, 'utf8')
52
- const allFiles = await walkTemplateDirs(ruleDir)
53
- return allFiles.filter(rel => {
54
- // Match markdown link to ./<rel> or (<rel>) anywhere in the .mdc
55
- return !mdc.includes(`./${rel}`) && !mdc.includes(`(${rel})`)
56
- })
57
- }
@@ -1,22 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: check-mdc-template-refs.mjs
4
- resource: npm/scripts/lib/check-mdc-template-refs.mjs
5
- docgen:
6
- crc: c116210e
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- ---
10
-
11
- Визначає список файлів шаблонів, що знаходяться у каталогах `fix` та `policy`, які не є цільовими посиланнями у файлі `<id>.mdc` як markdown link targets. Це дозволяє ідентифікувати ресурси шаблонів, які не були згадані в контексті правила.
12
-
13
- ## Поведінка
14
-
15
- 1. Збирає абсолютні шляхи всіх файлів, що знаходяться у каталогах `template` всередині підкаталогів `fix` та `policy` у вказаному каталозі правила.
16
- 2. Зчитує вміст файлу `main.mdc` у каталозі правила.
17
- 3. Фільтрує зібрані шляхи, залишаючи лише ті, які не з'являються у вмісті `main.mdc` у вигляді посилання Markdown (`./<шлях>` або ``).
18
- 4. Повертає відносні шляхи цих незгаданих шаблонних файлів відносно каталогу правила.
19
-
20
- ## Гарантії поведінки
21
-
22
- - Read-only: не виконує операцій запису (ФС/БД).