@nitra/cursor 12.11.3 → 12.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/bin/n-cursor.js +5 -22
  3. package/package.json +2 -1
  4. package/rules/bun/docs/index.md +2 -2
  5. package/rules/bun/docs/main.md +7 -8
  6. package/rules/bun/js/docs/index.md +3 -3
  7. package/rules/bun/main.json +1 -1
  8. package/rules/bun/main.mdc +3 -1
  9. package/rules/bun/main.mjs +24 -12
  10. package/rules/changelog/js/docs/index.md +3 -3
  11. package/rules/ci4/main.mdc +1 -0
  12. package/rules/ga/main.mdc +9 -1
  13. package/rules/js/js/docs/index.md +5 -5
  14. package/rules/js/main.mdc +4 -0
  15. package/rules/js-run/js/docs/index.md +3 -3
  16. package/rules/js-run/main.mdc +3 -0
  17. package/rules/npm-module/main.mdc +4 -0
  18. package/rules/python/docs/index.md +2 -2
  19. package/rules/python/docs/main.md +11 -10
  20. package/rules/python/main.mjs +49 -1
  21. package/rules/rego/main.mdc +2 -0
  22. package/rules/rust/docs/index.md +2 -2
  23. package/rules/rust/docs/main.md +8 -7
  24. package/rules/rust/main.json +1 -1
  25. package/rules/rust/main.mjs +16 -3
  26. package/rules/security/main.mdc +2 -0
  27. package/rules/style/js/docs/index.md +3 -3
  28. package/rules/style/main.mdc +5 -1
  29. package/rules/test/main.mdc +2 -1
  30. package/rules/text/main.mdc +9 -0
  31. package/scripts/docs/index.md +1 -0
  32. package/scripts/docs/update-blue-oak.md +28 -0
  33. package/scripts/lib/blue-oak.mjs +45 -0
  34. package/scripts/lib/discover-checkable-rules.mjs +7 -14
  35. package/scripts/lib/docs/blue-oak.md +29 -0
  36. package/scripts/lib/docs/index.md +35 -35
  37. package/scripts/lib/docs/run-rule.md +8 -6
  38. package/scripts/lib/fix/discover-t0-patterns.mjs +15 -48
  39. package/scripts/lib/fix/docs/index.md +12 -12
  40. package/scripts/lib/fix/docs/llm-worker.md +13 -8
  41. package/scripts/lib/fix/docs/t0.md +7 -7
  42. package/scripts/lib/fix/llm-worker.mjs +83 -3
  43. package/scripts/lib/fix/t0.mjs +1 -2
  44. package/scripts/lib/inline-template-links.mjs +9 -17
  45. package/scripts/lib/list-project-rules-mdc.mjs +3 -3
  46. package/scripts/lib/run-rule.mjs +0 -11
  47. package/scripts/update-blue-oak.mjs +51 -0
  48. package/scripts/utils/walkDir.mjs +32 -72
  49. package/skills/doc-aggregate/SKILL.md +1 -0
  50. package/skills/publish-telegram/SKILL.md +26 -28
  51. package/scripts/lib/check-mdc-template-refs.mjs +0 -57
  52. package/scripts/lib/docs/check-mdc-template-refs.md +0 -22
@@ -19,4 +19,5 @@ resource: npm/scripts/
19
19
  | [skills-cli.mjs](skills-cli.md) | JS Module |
20
20
  | [sync-claude-config.mjs](sync-claude-config.md) | JS Module |
21
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 |
22
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
+ }
@@ -17,6 +17,7 @@
17
17
  import { existsSync } from 'node:fs'
18
18
  import { readdir } from 'node:fs/promises'
19
19
  import { join } from 'node:path'
20
+ import { globby } from 'globby'
20
21
 
21
22
  /**
22
23
  * @typedef {object} JsConcern
@@ -44,20 +45,12 @@ import { join } from 'node:path'
44
45
  */
45
46
  async function listJsConcerns(jsDir) {
46
47
  if (!existsSync(jsDir)) return []
47
- const entries = await readdir(jsDir, { withFileTypes: true })
48
- /** @type {JsConcern[]} */
49
- const concerns = []
50
- for (const entry of entries) {
51
- if (!entry.isFile()) continue
52
- if (!entry.name.endsWith('.mjs')) continue
53
- if (entry.name.endsWith('.test.mjs')) continue
54
- if (entry.name.startsWith('fix-')) continue
55
- if (entry.name.startsWith('_')) continue
56
- if (entry.name.startsWith('.')) continue
57
- const name = entry.name.slice(0, -'.mjs'.length)
58
- concerns.push({ name })
59
- }
60
- return concerns.toSorted((a, b) => a.name.localeCompare(b.name))
48
+ const files = await globby(['*.mjs', '!*.test.mjs', '!fix-*.mjs', '!_*'], {
49
+ cwd: jsDir,
50
+ onlyFiles: true,
51
+ gitignore: false
52
+ })
53
+ return files.map(f => ({ name: f.slice(0, -4) })).toSorted((a, b) => a.name.localeCompare(b.name))
61
54
  }
62
55
 
63
56
  /**
@@ -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: не виконує операцій запису (ФС/БД).
@@ -6,39 +6,39 @@ resource: npm/scripts/lib/
6
6
 
7
7
  # npm/scripts/lib
8
8
 
9
- | Файл | Тип |
10
- |---|---|
11
- | [assert-project-root.mjs](assert-project-root.md) | JS Module |
12
- | [changed-files.mjs](changed-files.md) | JS Module |
13
- | [check-mdc-template-refs.mjs](check-mdc-template-refs.md) | JS Module |
14
- | [check-reporter.mjs](check-reporter.md) | JS Module |
15
- | [diff-added-lines.mjs](diff-added-lines.md) | JS Module |
9
+ | Файл | Тип |
10
+ | --------------------------------------------------------------------------- | --------- |
11
+ | [assert-project-root.mjs](assert-project-root.md) | JS Module |
12
+ | [blue-oak.mjs](blue-oak.md) | JS Module |
13
+ | [changed-files.mjs](changed-files.md) | JS Module |
14
+ | [check-reporter.mjs](check-reporter.md) | JS Module |
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 |
17
- | [discover-checkable-rules.mjs](discover-checkable-rules.md) | JS Module |
18
- | [ensure-tool.mjs](ensure-tool.md) | JS Module |
19
- | [generated-markdown.mjs](generated-markdown.md) | JS Module |
20
- | [gha-workflow.mjs](gha-workflow.md) | JS Module |
21
- | [inline-template-links.mjs](inline-template-links.md) | JS Module |
22
- | [list-project-rules-mdc.mjs](list-project-rules-mdc.md) | JS Module |
23
- | [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
24
- | [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
25
- | [mirror-parity.mjs](mirror-parity.md) | JS Module |
26
- | [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md) | JS Module |
27
- | [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
28
- | [root-notice.mjs](root-notice.md) | JS Module |
29
- | [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
30
- | [rule-meta.mjs](rule-meta.md) | JS Module |
31
- | [rule-predicates.mjs](rule-predicates.md) | JS Module |
32
- | [run-conftest-batch.mjs](run-conftest-batch.md) | JS Module |
33
- | [run-lint-step.mjs](run-lint-step.md) | JS Module |
34
- | [run-lint.mjs](run-lint.md) | JS Module |
35
- | [run-rule-cli.mjs](run-rule-cli.md) | JS Module |
36
- | [run-rule.mjs](run-rule.md) | JS Module |
37
- | [run-standard-lint.mjs](run-standard-lint.md) | JS Module |
38
- | [run-standard-rule.mjs](run-standard-rule.md) | JS Module |
39
- | [skill-meta.mjs](skill-meta.md) | JS Module |
40
- | [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md) | JS Module |
41
- | [template.mjs](template.md) | JS Module |
42
- | [timing-summary.mjs](timing-summary.md) | JS Module |
43
- | [workspaces.mjs](workspaces.md) | JS Module |
44
- | [worktree-notice.mjs](worktree-notice.md) | JS Module |
17
+ | [discover-checkable-rules.mjs](discover-checkable-rules.md) | JS Module |
18
+ | [ensure-tool.mjs](ensure-tool.md) | JS Module |
19
+ | [generated-markdown.mjs](generated-markdown.md) | JS Module |
20
+ | [gha-workflow.mjs](gha-workflow.md) | JS Module |
21
+ | [inline-template-links.mjs](inline-template-links.md) | JS Module |
22
+ | [list-project-rules-mdc.mjs](list-project-rules-mdc.md) | JS Module |
23
+ | [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
24
+ | [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
25
+ | [mirror-parity.mjs](mirror-parity.md) | JS Module |
26
+ | [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md) | JS Module |
27
+ | [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
28
+ | [root-notice.mjs](root-notice.md) | JS Module |
29
+ | [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
30
+ | [rule-meta.mjs](rule-meta.md) | JS Module |
31
+ | [rule-predicates.mjs](rule-predicates.md) | JS Module |
32
+ | [run-conftest-batch.mjs](run-conftest-batch.md) | JS Module |
33
+ | [run-lint-step.mjs](run-lint-step.md) | JS Module |
34
+ | [run-lint.mjs](run-lint.md) | JS Module |
35
+ | [run-rule-cli.mjs](run-rule-cli.md) | JS Module |
36
+ | [run-rule.mjs](run-rule.md) | JS Module |
37
+ | [run-standard-lint.mjs](run-standard-lint.md) | JS Module |
38
+ | [run-standard-rule.mjs](run-standard-rule.md) | JS Module |
39
+ | [skill-meta.mjs](skill-meta.md) | JS Module |
40
+ | [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md) | JS Module |
41
+ | [template.mjs](template.md) | JS Module |
42
+ | [timing-summary.mjs](timing-summary.md) | JS Module |
43
+ | [workspaces.mjs](workspaces.md) | JS Module |
44
+ | [worktree-notice.mjs](worktree-notice.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
 
@@ -6,42 +6,13 @@
6
6
  * `t0.mjs` ініціалізує результат через top-level await (один раз при завантаженні модуля).
7
7
  */
8
8
  import { existsSync } from 'node:fs'
9
- import { readdir } from 'node:fs/promises'
10
9
  import { join } from 'node:path'
10
+ import { globby } from 'globby'
11
11
 
12
12
  /**
13
13
  * @typedef {{ id: string, test: (output: string) => boolean, apply: (output: string, cwd: string) => Promise<{ok: boolean, action: string}> | {ok: boolean, action: string} }} T0Pattern
14
14
  */
15
15
 
16
- /**
17
- * Повертає абсолютні шляхи до `fix-*.mjs` файлів у директорії (плоско, без рекурсії).
18
- * @param {string} dir абсолютний шлях до директорії
19
- * @returns {Promise<string[]>}
20
- */
21
- async function findFixFiles(dir) {
22
- if (!existsSync(dir)) return []
23
- const entries = await readdir(dir, { withFileTypes: true })
24
- return entries
25
- .filter(e => e.isFile() && e.name.startsWith('fix-') && e.name.endsWith('.mjs'))
26
- .map(e => join(dir, e.name))
27
- }
28
-
29
- /**
30
- * Повертає абсолютні шляхи до `policy/{concern}/fix-*.mjs` у правилі.
31
- * @param {string} policyDir абсолютний шлях `rules/{rule}/policy/`
32
- * @returns {Promise<string[]>}
33
- */
34
- async function findPolicyFixFiles(policyDir) {
35
- if (!existsSync(policyDir)) return []
36
- const entries = await readdir(policyDir, { withFileTypes: true })
37
- const paths = []
38
- for (const entry of entries) {
39
- if (!entry.isDirectory()) continue
40
- paths.push(...(await findFixFiles(join(policyDir, entry.name))))
41
- }
42
- return paths
43
- }
44
-
45
16
  /**
46
17
  * Збирає всі T0-паттерни з `fix-*.mjs` файлів усіх правил у `rulesDir`.
47
18
  * @param {string} rulesDir абсолютний шлях до `npm/rules/`
@@ -49,26 +20,22 @@ async function findPolicyFixFiles(policyDir) {
49
20
  */
50
21
  export async function discoverT0Patterns(rulesDir) {
51
22
  if (!existsSync(rulesDir)) return []
52
- const ruleEntries = await readdir(rulesDir, { withFileTypes: true })
53
- /** @type {T0Pattern[]} */
54
- const allPatterns = []
55
-
56
- for (const ruleEntry of ruleEntries) {
57
- if (!ruleEntry.isDirectory() || ruleEntry.name.startsWith('.')) continue
58
- const ruleDir = join(rulesDir, ruleEntry.name)
59
23
 
60
- const fixPaths = [
61
- ...(await findFixFiles(join(ruleDir, 'js'))),
62
- ...(await findPolicyFixFiles(join(ruleDir, 'policy')))
63
- ]
24
+ const relPaths = await globby(['*/js/fix-*.mjs', '*/policy/*/fix-*.mjs'], {
25
+ cwd: rulesDir,
26
+ onlyFiles: true,
27
+ gitignore: false
28
+ })
64
29
 
65
- for (const fixPath of fixPaths) {
66
- try {
67
- const mod = await import(fixPath)
68
- if (Array.isArray(mod.patterns)) allPatterns.push(...mod.patterns)
69
- } catch (err) {
70
- console.error(`[discover-t0-patterns] не вдалося імпортувати ${fixPath}: ${err.message}`)
71
- }
30
+ /** @type {T0Pattern[]} */
31
+ const allPatterns = []
32
+ for (const rel of relPaths) {
33
+ const fixPath = join(rulesDir, rel)
34
+ try {
35
+ const mod = await import(fixPath)
36
+ if (Array.isArray(mod.patterns)) allPatterns.push(...mod.patterns)
37
+ } catch (err) {
38
+ console.error(`[discover-t0-patterns] не вдалося імпортувати ${fixPath}: ${err.message}`)
72
39
  }
73
40
  }
74
41
 
@@ -6,16 +6,16 @@ resource: npm/scripts/lib/fix/
6
6
 
7
7
  # npm/scripts/lib/fix
8
8
 
9
- | Файл | Тип |
10
- |---|---|
11
- | [analyze-escalation.mjs](analyze-escalation.md) | JS Module |
12
- | [discover-t0-patterns.mjs](discover-t0-patterns.md) | JS Module |
13
- | [escalation-log.mjs](escalation-log.md) | JS Module |
14
- | [llm-fix-apply.mjs](llm-fix-apply.md) | JS Module |
15
- | [llm-lint-fix.mjs](llm-lint-fix.md) | JS Module |
16
- | [llm-worker.mjs](llm-worker.md) | JS Module |
17
- | [orchestrator.mjs](orchestrator.md) | JS Module |
9
+ | Файл | Тип |
10
+ | ----------------------------------------------------- | --------- |
11
+ | [analyze-escalation.mjs](analyze-escalation.md) | JS Module |
12
+ | [discover-t0-patterns.mjs](discover-t0-patterns.md) | JS Module |
13
+ | [escalation-log.mjs](escalation-log.md) | JS Module |
14
+ | [llm-fix-apply.mjs](llm-fix-apply.md) | JS Module |
15
+ | [llm-lint-fix.mjs](llm-lint-fix.md) | JS Module |
16
+ | [llm-worker.mjs](llm-worker.md) | JS Module |
17
+ | [orchestrator.mjs](orchestrator.md) | JS Module |
18
18
  | [run-conformance-check.mjs](run-conformance-check.md) | JS Module |
19
- | [t0.mjs](t0.md) | JS Module |
20
- | [verbose-block.mjs](verbose-block.md) | JS Module |
21
- | [vscode-ext-add.mjs](vscode-ext-add.md) | JS Module |
19
+ | [t0.mjs](t0.md) | JS Module |
20
+ | [verbose-block.mjs](verbose-block.md) | JS Module |
21
+ | [vscode-ext-add.mjs](vscode-ext-add.md) | JS Module |
@@ -3,26 +3,31 @@ type: JS Module
3
3
  title: llm-worker.mjs
4
4
  resource: npm/scripts/lib/fix/llm-worker.mjs
5
5
  docgen:
6
- crc: 55419474
6
+ crc: 5d019a98
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль виділяє унікальні відносні шляхи файлів, пов'язаних із порушеннями. Він також виконує роботу з великою мовною моделлю для аналізу даних про порушення, зчитуючи правила та обробляючи відповідні файли.
13
+ Модуль виділяє унікальні відносні шляхи файлів, пов'язаних із порушеннями, та виконує LLM-виклик для виправлення одного rule-порушення. Підтримує вибір sub-check `.mdc` за конкретним target-файлом — замість повного `n-{id}.mdc` передає моделі лише релевантну секцію правила (1–2 KB замість 20+ KB).
14
14
 
15
15
  ## Поведінка
16
16
 
17
- extractFilePaths витягує унікальні відносні шляхи файлів із вихідних даних про порушення, розпізнаючи як явні файли, що потребують виправлення, так і файли, що надають контекст.
18
- runLlmWorker викликає LLM для виправлення одного порушення, зчитує відповідне правило, аналізує файли, формує промпт, отримує відповідь від моделі та застосовує виправлення.
17
+ `extractFilePaths` витягує унікальні відносні шляхи файлів із violation output, пріоритетно розбираючи рядки (файли що потребують фіксу), потім generic-regex для контексту. Розуміє workspace-prefix `[npm] path/file.ext → npm/path/file.ext`.
18
+
19
+ `selectSubCheckMdc` читає `policy/<concern>/target.json` з каталогу `npm/rules/{ruleId}/` пакету, знаходить concerns із `files.single`, що відповідають failing-файлам із violation output, і повертає конкатенацію відповідних `.mdc`. Повертає `null` якщо правило не має policy-підкаталогу або жоден concern не збігається з ❌-файлами.
20
+
21
+ `runLlmWorker` спочатку викликає `selectSubCheckMdc` — і якщо отримує match, передає моделі лише той sub-check `.mdc`. Якщо match немає — fallback на повний `n-{id}.mdc`. Далі читає файли з violation output, будує prompt, викликає LLM через `callLlmRich` і застосовує зміни.
19
22
 
20
23
  ## Публічний API
21
24
 
22
- extractFilePaths — виділяє відносні шляхи файлів з виводу помилок, обробляючи префікс робочого простору та пріоритетно парсячи рядки, що вказують на необхідність виправлення.
23
- runLlmWorker — виправляє одне порушення правила за допомогою LLM, повертаючи результати змін чи діагнозу, що слугує інформацією для наступних етапів процесу.
25
+ `extractFilePaths(output)`повертає унікальні відносні шляхи файлів з violation output (❌-рядки першими).
26
+
27
+ `runLlmWorker(ruleId, violationOutput, projectRoot, opts)` — виправляє одне порушення правила через LLM. Повертає `{ ok, error?, changes, diagnosis, reasoning, reasoningSource, promptSummary }`. `promptSummary.subCheckMdc: boolean` вказує чи використано точковий sub-check замість повного mdc.
24
28
 
25
29
  ## Гарантії поведінки
26
30
 
27
- - Read-only: не виконує операцій запису (ФС/БД).
28
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
31
+ - Читає `policy/` безпосередньо з пакету (`import.meta.dirname/../../../rules/`), без pre-generation файлів.
32
+ - Fallback на повний `n-{id}.mdc` при: відсутності policy-підкаталогу, `walkGlob`-таргетах, відсутності ❌-файлів у violation, нульовому match.
33
+ - Перехоплює всі помилки LLM і повертає структурований `{ ok: false, error }`.
@@ -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
  ## Гарантії поведінки
@@ -1,6 +1,6 @@
1
1
  /** @see ./docs/llm-worker.md */
2
2
 
3
- import { existsSync, readFileSync } from 'node:fs'
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
  import { env } from 'node:process'
6
6
  import { resolveModel } from '../../../lib/models.mjs'
@@ -20,6 +20,83 @@ const API_KEY_RE = /api key/i
20
20
 
21
21
  const FILE_EXTS = 'json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py'
22
22
 
23
+ /**
24
+ * Каталог `npm/rules/` у пакеті — для вибору sub-check .mdc.
25
+ * Шлях: <package>/npm/scripts/lib/fix/ → ../../.. → npm/ → rules/.
26
+ */
27
+ const PACKAGE_RULES_DIR = join(import.meta.dirname, '..', '..', '..', 'rules')
28
+
29
+ /**
30
+ * Витягує шляхи файлів лише з рядків ❌ у violation output.
31
+ * Без workspace-розгортання — повертає bare path для звірки з target.json.
32
+ * @param {string} output violation output
33
+ * @returns {string[]} унікальні шляхи з ❌-рядків
34
+ */
35
+ function extractFailPaths(output) {
36
+ const seen = new Set()
37
+ const add = p => {
38
+ seen.add(p)
39
+ }
40
+ const failSep = `(?::\\d+)?(?::\\s|[\\s—]|$)`
41
+ // ❌ [ws] path/file.ext → strip workspace, зберігаємо bare file
42
+ const failWsRe = new RegExp(`^\\s*❌\\s+\\[[\\w-]+\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
43
+ for (const m of output.matchAll(failWsRe)) add(m[1])
44
+ const failRe = new RegExp(`^\\s*❌\\s+(\\.?[\\w][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
45
+ for (const m of output.matchAll(failRe)) add(m[1])
46
+ return [...seen]
47
+ }
48
+
49
+ /**
50
+ * Для правил з `policy/<concern>/` підбирає лише ті concern-.mdc, що відповідають
51
+ * файлам з ❌-рядків violation output. Читає безпосередньо з пакету (`PACKAGE_RULES_DIR`),
52
+ * тому не потребує pre-generation. Fallback — null (→ повний n-{id}.mdc у caller).
53
+ * @param {string} ruleId ID правила
54
+ * @param {string} violationOutput violation output
55
+ * @returns {string|null} конкатенація релевантних .mdc або null
56
+ */
57
+ function selectSubCheckMdc(ruleId, violationOutput) {
58
+ const policyDir = join(PACKAGE_RULES_DIR, ruleId, 'policy')
59
+ if (!existsSync(policyDir)) return null
60
+
61
+ const failPaths = extractFailPaths(violationOutput)
62
+ if (failPaths.length === 0) return null
63
+
64
+ const matched = []
65
+ let concerns
66
+ try {
67
+ concerns = readdirSync(policyDir, { withFileTypes: true })
68
+ } catch {
69
+ return null
70
+ }
71
+
72
+ for (const entry of concerns) {
73
+ if (!entry.isDirectory()) continue
74
+ const concernDir = join(policyDir, entry.name)
75
+ const targetPath = join(concernDir, 'target.json')
76
+ if (!existsSync(targetPath)) continue
77
+
78
+ let target
79
+ try {
80
+ target = JSON.parse(readFileSync(targetPath, 'utf8'))
81
+ } catch {
82
+ continue
83
+ }
84
+
85
+ const targetFile = target?.files?.single
86
+ if (!targetFile) continue // walkGlob та інші типи → skip, fallback на main.mdc
87
+
88
+ // Перевіряємо чи хоч один failing path закінчується на targetFile
89
+ const hit = failPaths.some(p => p === targetFile || p.endsWith(`/${targetFile}`))
90
+ if (!hit) continue
91
+
92
+ const mdcEntry = readdirSync(concernDir).find(f => f.endsWith('.mdc'))
93
+ if (!mdcEntry) continue
94
+ matched.push(readFileSync(join(concernDir, mdcEntry), 'utf8').trim())
95
+ }
96
+
97
+ return matched.length > 0 ? matched.join('\n\n') : null
98
+ }
99
+
23
100
  /**
24
101
  * Витягує відносні шляхи файлів із violation output.
25
102
  * Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
@@ -185,9 +262,11 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
185
262
  const timeoutMs = opts.timeoutMs
186
263
  const thinkingBudget = opts.thinkingBudget ?? DEFAULT_THINKING_BUDGET
187
264
 
188
- // 1. Читаємо rule .mdc
265
+ // 1. Читаємо rule .mdc: спробуємо sub-check mdc для конкретної перевірки,
266
+ // якщо не вдалося — fallback на повний n-{id}.mdc.
267
+ const subMdc = selectSubCheckMdc(ruleId, violationOutput)
189
268
  const mdcPath = join(projectRoot, '.cursor', 'rules', `n-${ruleId}.mdc`)
190
- const ruleMdc = existsSync(mdcPath) ? readFileSync(mdcPath, 'utf8') : '(rule file not found)'
269
+ const ruleMdc = subMdc ?? (existsSync(mdcPath) ? readFileSync(mdcPath, 'utf8') : '(rule file not found)')
191
270
 
192
271
  // 2. Витягуємо файли з violation output і читаємо їх
193
272
  const files = readFilesForFix(extractFilePaths(violationOutput), projectRoot)
@@ -195,6 +274,7 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
195
274
  // 3. Будуємо summary промпту (для verbose-блоку) до виклику моделі
196
275
  const promptSummary = {
197
276
  ruleMdcLen: ruleMdc.length,
277
+ subCheckMdc: !!subMdc,
198
278
  violationLen: violationOutput.length,
199
279
  filesCount: files.length,
200
280
  filesTotalBytes: files.reduce((s, f) => s + f.content.length, 0),
@@ -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
  }