@nitra/cursor 12.11.0 → 12.11.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 (71) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +9 -27
  3. package/package.json +1 -1
  4. package/rules/adr/js/docs/hooks.md +0 -2
  5. package/rules/bun/js/docs/fix-layout.md +25 -0
  6. package/rules/bun/js/fix-layout.mjs +55 -0
  7. package/rules/changelog/js/docs/consistency.md +11 -13
  8. package/rules/changelog/js/docs/fix-consistency.md +27 -0
  9. package/rules/changelog/js/docs/index.md +2 -2
  10. package/rules/changelog/js/fix-consistency.mjs +50 -0
  11. package/rules/ci4/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  12. package/rules/ci4/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  13. package/rules/ga/policy/vscode_extensions/docs/fix-vscode_extensions.md +22 -0
  14. package/rules/ga/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  15. package/rules/ga/policy/workflow_common/workflow_common.rego +15 -0
  16. package/rules/graphql/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  17. package/rules/graphql/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  18. package/rules/js/js/docs/dep-policy.md +12 -10
  19. package/rules/js/policy/vscode_extensions/docs/fix-vscode_extensions.md +22 -0
  20. package/rules/js/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  21. package/rules/js-run/js/docs/fix-runtime.md +25 -0
  22. package/rules/js-run/js/fix-runtime.mjs +41 -0
  23. package/rules/k8s/policy/lint_k8s_yml/lint_k8s_yml.rego +57 -0
  24. package/rules/k8s/policy/lint_k8s_yml/target.json +4 -0
  25. package/rules/k8s/policy/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +43 -0
  26. package/rules/nginx-default-tpl/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  27. package/rules/nginx-default-tpl/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  28. package/rules/rego/policy/vscode_extensions/docs/fix-vscode_extensions.md +22 -0
  29. package/rules/rego/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  30. package/rules/rust/policy/vscode_extensions/docs/fix-vscode_extensions.md +22 -0
  31. package/rules/rust/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  32. package/rules/style/js/docs/fix-tooling.md +29 -0
  33. package/rules/style/js/fix-tooling.mjs +46 -0
  34. package/rules/style/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  35. package/rules/style/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  36. package/rules/tauri/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  37. package/rules/tauri/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  38. package/rules/text/policy/vscode_extensions/docs/fix-vscode_extensions.md +21 -0
  39. package/rules/text/policy/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  40. package/rules/vue/js/docs/packages.md +0 -2
  41. package/scripts/docs/index.md +0 -2
  42. package/scripts/lib/discover-checkable-rules.mjs +1 -0
  43. package/scripts/lib/docs/discover-checkable-rules.md +13 -155
  44. package/scripts/lib/fix/discover-t0-patterns.mjs +83 -0
  45. package/scripts/lib/fix/docs/discover-t0-patterns.md +37 -0
  46. package/scripts/lib/fix/docs/llm-fix-apply.md +12 -10
  47. package/scripts/lib/fix/docs/llm-worker.md +6 -14
  48. package/scripts/lib/fix/docs/orchestrator.md +0 -2
  49. package/scripts/lib/fix/docs/t0.md +11 -10
  50. package/scripts/lib/fix/docs/vscode-ext-add.md +29 -0
  51. package/scripts/lib/fix/llm-fix-apply.mjs +34 -3
  52. package/scripts/lib/fix/llm-worker.mjs +24 -15
  53. package/scripts/lib/fix/t0.mjs +8 -119
  54. package/scripts/lib/fix/vscode-ext-add.mjs +45 -0
  55. package/rules/test/coverage/coverage.mjs +0 -317
  56. package/scripts/coverage-classify/apply.mjs +0 -67
  57. package/scripts/coverage-classify/cache.mjs +0 -77
  58. package/scripts/coverage-classify/docs/apply.md +0 -206
  59. package/scripts/coverage-classify/docs/cache.md +0 -207
  60. package/scripts/coverage-classify/docs/index.md +0 -14
  61. package/scripts/coverage-classify/docs/prompt.md +0 -136
  62. package/scripts/coverage-classify/docs/verdict-schema.md +0 -28
  63. package/scripts/coverage-classify/index.mjs +0 -114
  64. package/scripts/coverage-classify/prompt.mjs +0 -126
  65. package/scripts/coverage-classify/verdict-schema.mjs +0 -35
  66. package/scripts/coverage-fix-extract.mjs +0 -122
  67. package/scripts/coverage-fix.mjs +0 -119
  68. package/scripts/docs/coverage-fix-extract.md +0 -36
  69. package/scripts/docs/coverage-fix.md +0 -181
  70. package/skills/coverage-fix/SKILL.md +0 -131
  71. package/skills/coverage-fix/main.json +0 -1
@@ -3,27 +3,29 @@ type: JS Module
3
3
  title: llm-fix-apply.mjs
4
4
  resource: npm/scripts/lib/fix/llm-fix-apply.mjs
5
5
  docgen:
6
- crc: 62e475fa
6
+ crc: 49f989ca
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Модуль є спільним ядром для застосування виправлень, згенерованих LLM, використовуючи конформні (`llm-worker.mjs`) та інструментальні (`llm-lint-fix.mjs`) механізми для уникнення дублювання логіки парсингу та застосування змін. Він парсить структуровану відповідь моделі, що містить список змін у форматі `{changes:[{path,content}]}`. Далі, він зчитує вміст файлів, зазначених у цих змінах, та застосовує оновлений вміст до відповідних файлів. Усі операції реалізовані з механізмом fail-safe: при невдачах функції повертають значення помилки (false/null/Err) замість викидання винятків.
11
+ ## Огляд
12
+
13
+ Це спільне ядро LLM-фіксу, призначене для оркестрації процесу застосування змін, згенерованих моделлю. Модуль використовує `llm-worker.mjs` та `llm-lint-fix.mjs` для забезпечення конформності та виконання лінтер-фіксів. Основний функціонал включає: парсинг відповіді LLM за схемою `{changes:[{path,content}]}` (через `parseChangesResponse`), зчитування необхідних файлів (`readFilesForFix`), та безпечне застосування змін (`applyChanges`). Система реалізує механізм fail-safe, перехоплюючи помилки та повертаючи `null` замість винятків для певних сценаріїв. При цьому шляхи `.git` та `node_modules` свідомо ігноруються.
12
14
 
13
15
  ## Поведінка
14
16
 
15
- parseChangesResponse парсить сирий текст відповіді моделі, щоб витягнути структуру змін у форматі патчу або повернути null у разі невдачі парсингу.
16
- readFilesForFix зчитує вміст файлів, зазначених у списку відносних шляхів, відносно кореня проєкту, повертаючи список об'єктів з шляхом та вмістом або null для неіснуючих файлів.
17
- applyChanges записує нові вмісти в файли, використовуючи наданий список змін, відносно кореня проєкту; перед записом створює батьківську теку (`mkdirSync recursive`) — модель може запропонувати новий файл у ще неіснуючому каталозі. Повертає статус успіху або помилки.
17
+ parseChangesResponse парсить сирий текст відповіді моделі, витягуючи структуру змін у форматі патчу, або повертає null, якщо парсинг неможливий.
18
+ readFilesForFix читає вміст файлів, вказаних у списку, відносно кореня проєкту. Якщо прямий шлях не знайдено, шукає файл за його базовим ім'ям у проєкті, ігноруючи каталоги `.git` та `node_modules`.
19
+ applyChanges записує вміст, наданий у змінних, у відповідні файли проєкту, створюючи необхідні каталоги, якщо вони відсутні.
18
20
 
19
21
  ## Публічний API
20
22
 
21
- parseChangesResponse — витягує структуру змін із JSON-відповіді моделі.
22
- readFilesForFix — зчитує вміст файлів за заданими шляхами для використання у запиті.
23
- applyChanges — замінює вміст файлів на нові дані, отримані у змінних змін.
23
+ parseChangesResponse — розбирає JSON-відповідь моделі, витягуючи вміст першого об'єкта.
24
+ readFilesForFix — зчитує вміст файлів за заданими шляхами, шукаючи їх у системі, якщо прямий шлях не знайдено.
25
+ applyChanges — замінює вміст файлів на новий, наданий у змінній `changes`.
24
26
 
25
27
  ## Гарантії поведінки
26
28
 
27
29
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
28
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
29
- - Не звертається до мережі.
30
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
31
+ - Свідомо пропускає шляхи: `.git`, `node_modules`.
@@ -3,32 +3,24 @@ type: JS Module
3
3
  title: llm-worker.mjs
4
4
  resource: npm/scripts/lib/fix/llm-worker.mjs
5
5
  docgen:
6
- crc: d6c0b516
6
+ crc: 55419474
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль взаємодіє з мовною моделлю для обробки правил та файлів. Він формує запит до моделі, отримує пропозиції щодо змін, які не застосовуються до файлової системи. Функція обробки гарантує перехоплення помилок (fail-safe) та не генерує винятків назовні.
13
+ Модуль виділяє унікальні відносні шляхи файлів, пов'язаних із порушеннями. Він також виконує роботу з великою мовною моделлю для аналізу даних про порушення, зчитуючи правила та обробляючи відповідні файли.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- 1. Викликає runLlmWorker для виправлення однієї проблеми.
18
- 2. Зчитує вміст відповідного файлу правила.
19
- 3. Витягує шляхи до файлів, які порушені, з вихідних даних про порушення.
20
- 4. Зчитує вміст цих файлів.
21
- 5. Формує повний текстовий запит для моделі, включаючи правило, вихідні дані про порушення, вміст файлів та, за наявності, контекст попередньої спроби.
22
- 6. Відправляє цей запит моделі.
23
- 7. У разі помилки при виклику моделі, повертає статус невдачі з деталями помилки.
24
- 8. Якщо модель відповідає, парсить її відповідь, щоб отримати діагноз та список змін.
25
- 9. У разі неможливості розібрати відповідь або відсутності змін, повертає статус невдачі.
26
- 10. Якщо зміни успішно отримані, застосовує ці зміни до файлової системи проєкту.
27
- 11. Повертає результат, що містить статус успіху, список застосованих змін, діагноз та метадані про запит.
17
+ extractFilePaths витягує унікальні відносні шляхи файлів із вихідних даних про порушення, розпізнаючи як явні файли, що потребують виправлення, так і файли, що надають контекст.
18
+ runLlmWorker викликає LLM для виправлення одного порушення, зчитує відповідне правило, аналізує файли, формує промпт, отримує відповідь від моделі та застосовує виправлення.
28
19
 
29
20
  ## Публічний API
30
21
 
31
- runLlmWorkerвиправляє одне порушення правила, використовуючи шаблон C1. Повертає результати виправлення чи діагнозу незалежно від успіху, що дозволяє наступним етапам системи отримувати інформацію про спробу. Поля про міркування та підсумок запиту використовуються для детального виводу в режимі `--full`. Дозволяє перевизначити модель, передати контекст попередніх спроб, позначити джерело виклику та встановити ліміти часу для кожного рівня виконання. Керує обсягом токенів, доступних для процесу мислення.
22
+ extractFilePathsвиділяє відносні шляхи файлів з виводу помилок, обробляючи префікс робочого простору та пріоритетно парсячи рядки, що вказують на необхідність виправлення.
23
+ runLlmWorker — виправляє одне порушення правила за допомогою LLM, повертаючи результати змін чи діагнозу, що слугує інформацією для наступних етапів процесу.
32
24
 
33
25
  ## Гарантії поведінки
34
26
 
@@ -8,8 +8,6 @@ docgen:
8
8
  score: 100
9
9
  ---
10
10
 
11
- ## Огляд
12
-
13
11
  Модуль відповідає за управління процесом вирішення порушень. Він будує послідовність тирів ескалації за допомогою `buildLadder`. Функція `parseOrchestratorArgs` визначає бюджет LLM та фільтр правил. Далі, `runOrchestratorCli` виконує процес виправлення правил, послідовно застосовуючи `escalateRule` по тирах до досягнення першого успішного вирішення.
14
12
 
15
13
  ## Поведінка
@@ -3,26 +3,27 @@ type: JS Module
3
3
  title: t0.mjs
4
4
  resource: npm/scripts/lib/fix/t0.mjs
5
5
  docgen:
6
- crc: cfb0d5c9
6
+ crc: 92c9348d
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Модуль керує автоматичним застосуванням паттернів T0-auto до виводів порушень. Він використовує конфігурації з `extensions.json` та `package-lock.json` для визначення правил. Модуль фільтрує правила, які можуть бути автоматично оброблені, застосовує паттерни за допомогою `applyT0Auto`, а потім запускає повний цикл перевірки для всіх провальних правил через `runT0AutoCli`. Усі операції виконуються з механізмом fail-safe, що запобігає виникненню винятків назовні.
11
+ ## Огляд
12
+
13
+ Модуль забезпечує механізм автоматичного застосування паттернів T0-auto. Він дозволяє відфільтровувати правила, до яких застосовуються ці паттерни, та запускає повний цикл перевірки конформності з автоматичним виправленням через `runT0AutoCli`.
12
14
 
13
15
  ## Поведінка
14
16
 
15
- Поведінка
16
- applyT0Auto застосовує всі визначені T0-auto паттерни до одного виводу порушення, повертаючи результат застосування та список виконаних дій.
17
- filterT0AutoRules повертає список ID правил, для яких існує хоча б один T0-auto паттерн, виходячи з виводу порушення.
18
- runT0AutoCli запускає T0-auto для всіх провальних правил, повторно перевіряє конформність та виводить підсумок щодо закритого чи незакритого порушень.
17
+ applyT0Auto застосовує всі T0-auto паттерни до одного виявленого порушення, повертаючи результат застосування та список виконаних дій.
18
+ filterT0AutoRules повертає список ID правил, для яких існують відповідні T0-auto паттерни, виходячи з виявлених порушень.
19
+ runT0AutoCli запускає процес T0-auto: виконує перевірку конформності, застосовує T0-auto до порушень, повторно перевіряє змінені правила та виводить підсумок.
19
20
 
20
21
  ## Публічний API
21
22
 
22
- applyT0Auto — вносить зміни до виводу порушень, використовуючи всі визначені T0-auto шаблони.
23
- filterT0AutoRules — визначає, які правила мають відповідні T0-auto шаблони, аналізуючи вивід порушень, отриманий з `fix --json`.
24
- runT0AutoCli — виконує команду `n-cursor fix-t0 [rule...]`, що включає застосування T0-auto до кожного порушення, повторну перевірку check-gate та виведення результату.
23
+ applyT0Auto — застосовує всі T0-auto шаблони до вихідних даних про порушення.
24
+ filterT0AutoRules — визначає і повертає ідентифікатори правил, які мають принаймні один T0-auto шаблон, виходячи з результату `fix --json`.
25
+ runT0AutoCli — виконує команду `n-cursor fix-t0 [rule...]`, яка запускає `fix --json`, застосовує T0-auto до кожного порушення, повторно перевіряє check-gate та виводить звіт.
25
26
 
26
27
  ## Гарантії поведінки
27
28
 
28
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: vscode-ext-add.mjs
4
+ resource: npm/scripts/lib/fix/vscode-ext-add.mjs
5
+ docgen:
6
+ crc: 950e9098
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Реалізує Shared T0-autofix паттерн для правил із `vscode_extensions.rego`. Механізм витягує назви розширень із повідомлень про порушення та додає їх до секції `recommendations` у конфігурації `.vscode/extensions.json`, якщо вони відсутні. Паттерн універсальний для правил, що емітують вимогу «recommendations має містити "…"». Функціональність надається через публічні функції `patterns` та забезпечує перехоплення помилок для запобігання виняткам.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Перевіряє, чи повідомлення про порушення містить шаблон, що вказує на необхідність додати рекомендації.
18
+ 2. Витягує назви розширень з повідомлення про порушення.
19
+ 3. Перевіряє наявність файлу `.vscode/extensions.json` у поточній робочій директорії.
20
+ 4. Зчитує вміст `.vscode/extensions.json` та парсить його як JSON.
21
+ 5. Ініціалізує список існуючих рекомендацій.
22
+ 6. Визначає назви розширень, які потрібно додати, відфільтровувавши ті, що вже присутні.
23
+ 7. Якщо є розширення для додавання, оновлює список рекомендацій у парсереному об'єкті.
24
+ 8. Записує оновлений JSON-об'єкт назад у `.vscode/extensions.json`.
25
+ 9. Повертає результат виконання операції.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -3,8 +3,9 @@
3
3
  * під фікс і застосування змін. Використовують і `llm-worker.mjs` (конформність), і
4
4
  * `llm-lint-fix.mjs` (per-tool лінтер-фіксери) — щоб не дублювати парс/apply (knip/jscpd).
5
5
  */
6
+ import { execSync } from 'node:child_process'
6
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
7
- import { dirname, join } from 'node:path'
8
+ import { basename, dirname, join } from 'node:path'
8
9
 
9
10
  const JSON_CODE_BLOCK_RE = /```(?:json)?[ \t]{0,8}\n?([\s\S]*?)```/
10
11
 
@@ -39,8 +40,30 @@ export function parseChangesResponse(text) {
39
40
  return null
40
41
  }
41
42
 
43
+ /**
44
+ * Шукає файл за basename у дереві проєкту (fallback коли прямий шлях не існує).
45
+ * Повертає відносний шлях якщо знайдено рівно один матч, інакше `null` (ambiguous/not found).
46
+ * @param {string} name basename файлу
47
+ * @param {string} projectRoot абсолютний корінь
48
+ * @returns {string|null} відносний шлях або null
49
+ */
50
+ function findByBasename(name, projectRoot) {
51
+ try {
52
+ const raw = execSync(
53
+ `find . -maxdepth 7 -name '${name.replace(/'/g, "'\\''")}' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.worktrees/*'`,
54
+ { cwd: projectRoot, encoding: 'utf8', timeout: 3000 }
55
+ ).trim()
56
+ const hits = raw.split('\n').filter(Boolean)
57
+ return hits.length === 1 ? hits[0].replace(/^\.\//, '') : null
58
+ } catch {
59
+ return null
60
+ }
61
+ }
62
+
42
63
  /**
43
64
  * Читає існуючі файли за відносними шляхами у форму `{path, content}` (для prompt).
65
+ * Якщо файл не знайдений за прямим шляхом — намагається знайти за basename через `find`.
66
+ * Повертає resolved path (може відрізнятись від вхідного коли `find` знайшов реальне місце).
44
67
  * @param {string[]} filePaths відносні шляхи від кореня
45
68
  * @param {string} projectRoot абсолютний корінь
46
69
  * @returns {Array<{path:string, content:string}>} наявні файли з вмістом
@@ -48,10 +71,18 @@ export function parseChangesResponse(text) {
48
71
  export function readFilesForFix(filePaths, projectRoot) {
49
72
  return filePaths
50
73
  .map(p => {
51
- const abs = join(projectRoot, p)
74
+ let abs = join(projectRoot, p)
75
+ let resolvedPath = p
76
+ if (!existsSync(abs)) {
77
+ const found = findByBasename(basename(p), projectRoot)
78
+ if (found) {
79
+ resolvedPath = found
80
+ abs = join(projectRoot, found)
81
+ }
82
+ }
52
83
  if (!existsSync(abs)) return null
53
84
  try {
54
- return { path: p, content: readFileSync(abs, 'utf8') }
85
+ return { path: resolvedPath, content: readFileSync(abs, 'utf8') }
55
86
  } catch {
56
87
  return null
57
88
  }
@@ -18,35 +18,44 @@ const DEFAULT_THINKING_BUDGET = Number(env.N_CURSOR_OMLX_THINKING_BUDGET ?? 4096
18
18
 
19
19
  const API_KEY_RE = /api key/i
20
20
 
21
+ const FILE_EXTS = 'json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py'
22
+
21
23
  /**
22
24
  * Витягує відносні шляхи файлів із violation output.
23
25
  * Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
26
+ * Спочатку явно парсить рядки ❌ (найвищий сигнал — файл потребує фіксу),
27
+ * потім підхоплює решту файлів generic-regex (контекст для читання).
24
28
  * @param {string} output violation output з fix check
25
29
  * @returns {string[]} унікальні відносні шляхи (від кореня проєкту)
26
30
  */
27
- function extractFilePaths(output) {
31
+ export function extractFilePaths(output) {
28
32
  const seen = new Set()
29
33
  const results = []
30
-
31
- // Патерн з workspace: [npm] skills/foo.mjs або [demo] src/bar.ts
32
- const wsRe = /\[([\w-]+)\]\s+([\w./][\w./-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
33
- for (const m of output.matchAll(wsRe)) {
34
- const p = `${m[1]}/${m[2]}`
34
+ const add = p => {
35
35
  if (!seen.has(p)) {
36
36
  seen.add(p)
37
37
  results.push(p)
38
38
  }
39
39
  }
40
40
 
41
- // Патерн без workspace: просто path/to/file.ext або ./file.ext
42
- const re = /(?:^|\s)(\.?\w[\w./-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
43
- for (const m of output.matchAll(re)) {
44
- const p = m[1]
45
- if (!seen.has(p)) {
46
- seen.add(p)
47
- results.push(p)
48
- }
49
- }
41
+ // 1. Явні рядки найвищий сигнал: саме ці файли потребують фіксу.
42
+ // Формати: `❌ [ws] path/file.ext:line — msg` та `❌ path/file.ext: msg`
43
+ // Роздільник після шляху: `:` (з пробілом або цифрою), `—` (em-dash), або кінець рядка.
44
+ const failSep = `(?::\\d+)?(?::\\s|[\\s—]|$)`
45
+ const failWsRe = new RegExp(`^\\s*❌\\s+\\[([\\w-]+)\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
46
+ for (const m of output.matchAll(failWsRe)) add(`${m[1]}/${m[2]}`)
47
+
48
+ const failRe = new RegExp(`^\\s*❌\\s+(\\.?[\\w][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
49
+ for (const m of output.matchAll(failRe)) add(m[1])
50
+
51
+ // 2. Generic-regex: підхоплює файли з ✅-рядків та описів (контекст для читання).
52
+ // Workspace: [npm] skills/foo.mjs
53
+ const wsRe = new RegExp(`\\[([\\w-]+)\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))(?::\\d+)?`, 'gm')
54
+ for (const m of output.matchAll(wsRe)) add(`${m[1]}/${m[2]}`)
55
+
56
+ // Без workspace: path/to/file.ext або ./file.ext
57
+ const re = new RegExp(`(?:^|\\s)(\\.?\\w[\\w./-]*\\.(?:${FILE_EXTS}))(?::\\d+)?`, 'gm')
58
+ for (const m of output.matchAll(re)) add(m[1])
50
59
 
51
60
  return results
52
61
  }
@@ -1,123 +1,14 @@
1
1
  /** @see ./docs/t0.md */
2
- import { spawnSync } from 'node:child_process'
3
- import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
- import { join } from 'node:path'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
5
4
 
5
+ import { discoverT0Patterns } from './discover-t0-patterns.mjs'
6
6
  import { runConformanceCheck } from './run-conformance-check.mjs'
7
- import { writeChange } from '../../../rules/release/change.mjs'
8
-
9
- const REC_REQUIRE_RE = /recommendations має містити "[^"]+"/
10
- const REC_MATCH_ALL_RE = /recommendations має містити "([^"]+)"/g
11
- const FORBIDDEN_FILE_RE = /Знайдено заборонений файл: \S+/
12
- const FORBIDDEN_FILE_MATCH_ALL_RE = /Знайдено заборонений файл: (\S+)/g
13
- // Конформність changelog: «<ws>: є релевантні зміни, але немає change-файлу».
14
- const MISSING_CHANGE_RE = /є релевантні зміни, але немає change-файлу/
15
- const MISSING_CHANGE_MATCH_ALL_RE = /(?:^|\s)([\w./@-]+): є релевантні зміни, але немає change-файлу/gm
16
- /** Дефолти autofix-створеного change-файлу (узгоджено з n-changelog.mdc / consistency.mjs). */
17
- const CHANGE_BUMP = 'patch'
18
- const CHANGE_SECTION = 'Changed'
19
- const CHANGE_FALLBACK_MESSAGE = 'оновлення'
20
7
 
21
- /**
22
- * Опис для авто-створеного change-файлу: subject останнього коміту, інакше fallback.
23
- * @param {string} cwd корінь репозиторію
24
- * @returns {string} непорожній опис
25
- */
26
- function autoChangeMessage(cwd) {
27
- const r = spawnSync('git', ['log', '-1', '--format=%s'], { cwd, encoding: 'utf8' })
28
- const subject = r.status === 0 ? (r.stdout ?? '').trim() : ''
29
- return subject || CHANGE_FALLBACK_MESSAGE
30
- }
31
-
32
- /**
33
- * Патерни T0-auto.
34
- * Кожен паттерн: {
35
- * id: string — унікальна назва паттерну (для логу)
36
- * test: (output)=>bool — чи підходить цей output до паттерну
37
- * apply: (match, cwd)=>{ ok: bool, action: string } — застосувати фікс
38
- * }
39
- */
40
- const PATTERNS = [
41
- // ── vscode-ext-add ──────────────────────────────────────────────────────────
42
- // Violation: «recommendations має містити "tsandall.opa"»
43
- // Fix: додати рядок у .vscode/extensions.json#recommendations
44
- {
45
- id: 'vscode-ext-add',
46
- test: out => REC_REQUIRE_RE.test(out),
47
- apply: (out, cwd) => {
48
- const matches = [...out.matchAll(REC_MATCH_ALL_RE)]
49
- if (matches.length === 0) return { ok: false, action: 'no match' }
50
-
51
- const extPath = join(cwd, '.vscode/extensions.json')
52
- if (!existsSync(extPath)) {
53
- return { ok: false, action: '.vscode/extensions.json не знайдено' }
54
- }
55
-
56
- let parsed
57
- try {
58
- parsed = JSON.parse(readFileSync(extPath, 'utf8'))
59
- } catch {
60
- return { ok: false, action: '.vscode/extensions.json: невалідний JSON' }
61
- }
62
-
63
- const recs = Array.isArray(parsed.recommendations) ? parsed.recommendations : []
64
- const toAdd = matches.map(m => m[1]).filter(e => !recs.includes(e))
65
- if (toAdd.length === 0) return { ok: false, action: 'вже є' }
66
-
67
- parsed.recommendations = [...recs, ...toAdd]
68
- writeFileSync(extPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8')
69
- return { ok: true, action: `додано до extensions.json: ${toAdd.join(', ')}` }
70
- }
71
- },
72
-
73
- // ── rm-forbidden-file ────────────────────────────────────────────────────────
74
- // Violation: «Знайдено заборонений файл: package-lock.json»
75
- // Fix: видалити файл
76
- {
77
- id: 'rm-forbidden-file',
78
- test: out => FORBIDDEN_FILE_RE.test(out),
79
- apply: (out, cwd) => {
80
- const matches = [...out.matchAll(FORBIDDEN_FILE_MATCH_ALL_RE)]
81
- if (matches.length === 0) return { ok: false, action: 'no match' }
82
-
83
- const removed = []
84
- for (const m of matches) {
85
- const filePath = join(cwd, m[1])
86
- if (existsSync(filePath)) {
87
- rmSync(filePath, { force: true })
88
- removed.push(m[1])
89
- }
90
- }
91
- if (removed.length === 0) return { ok: false, action: 'файлів не знайдено' }
92
- return { ok: true, action: `видалено: ${removed.join(', ')}` }
93
- }
94
- },
95
-
96
- // ── changelog-create-change-file ─────────────────────────────────────────────
97
- // Violation: «<ws>: є релевантні зміни, але немає change-файлу»
98
- // Fix: створити change-файл через канонічну `writeChange` (без LLM) — той самий
99
- // механізм, що autofix changelog-конформності. Прибирає ескалацію в хмару на цьому кейсі.
100
- {
101
- id: 'changelog-create-change-file',
102
- test: out => MISSING_CHANGE_RE.test(out),
103
- apply: async (out, cwd) => {
104
- const workspaces = Array.from(out.matchAll(MISSING_CHANGE_MATCH_ALL_RE), m => m[1])
105
- if (workspaces.length === 0) return { ok: false, action: 'no match' }
106
-
107
- const message = autoChangeMessage(cwd)
108
- const created = []
109
- for (const ws of workspaces) {
110
- try {
111
- const rel = await writeChange({ bump: CHANGE_BUMP, section: CHANGE_SECTION, message, ws, cwd })
112
- created.push(ws === '.' ? rel : join(ws, rel))
113
- } catch (error) {
114
- return { ok: false, action: `writeChange ${ws}: ${error.message}` }
115
- }
116
- }
117
- return { ok: true, action: `створено change-файл (${CHANGE_BUMP}/${CHANGE_SECTION}): ${created.join(', ')}` }
118
- }
119
- }
120
- ]
8
+ // Паттерни живуть у rule-level fix-*.mjs файлах; агрегуємо тут через discovery.
9
+ // Top-level await: ініціалізація один раз при завантаженні модуля.
10
+ const RULES_DIR = join(dirname(fileURLToPath(import.meta.url)), '../../../rules')
11
+ const PATTERNS = await discoverT0Patterns(RULES_DIR)
121
12
 
122
13
  /**
123
14
  * Застосовує всі T0-auto паттерни до одного violation-output.
@@ -135,9 +26,7 @@ export async function applyT0Auto(ruleId, violationOutput, cwd) {
135
26
  // Патерн може бути sync ({ok,action}) або async (Promise) — await нормалізує обидва.
136
27
  const result = await p.apply(violationOutput, cwd)
137
28
  actions.push(`[${p.id}] ${result.action}`)
138
- if (result.ok) {
139
- applied = true
140
- }
29
+ if (result.ok) applied = true
141
30
  }
142
31
 
143
32
  return { applied, actions }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared T0-autofix паттерн для правил із `vscode_extensions.rego`.
3
+ * Читає назву розширення з violation-message і додає його до
4
+ * `.vscode/extensions.json#recommendations`.
5
+ *
6
+ * Не прив'язаний до конкретного правила — один механізм для всіх правил,
7
+ * що емітують «recommendations має містити "…"».
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
10
+ import { join } from 'node:path'
11
+
12
+ const REC_REQUIRE_RE = /recommendations має містити "[^"]+"/
13
+ const REC_MATCH_ALL_RE = /recommendations має містити "([^"]+)"/g
14
+
15
+ /** @type {import('./discover-t0-patterns.mjs').T0Pattern[]} */
16
+ export const patterns = [
17
+ {
18
+ id: 'vscode-ext-add',
19
+ test: out => REC_REQUIRE_RE.test(out),
20
+ apply: (out, cwd) => {
21
+ const matches = [...out.matchAll(REC_MATCH_ALL_RE)]
22
+ if (matches.length === 0) return { ok: false, action: 'no match' }
23
+
24
+ const extPath = join(cwd, '.vscode/extensions.json')
25
+ if (!existsSync(extPath)) {
26
+ return { ok: false, action: '.vscode/extensions.json не знайдено' }
27
+ }
28
+
29
+ let parsed
30
+ try {
31
+ parsed = JSON.parse(readFileSync(extPath, 'utf8'))
32
+ } catch {
33
+ return { ok: false, action: '.vscode/extensions.json: невалідний JSON' }
34
+ }
35
+
36
+ const recs = Array.isArray(parsed.recommendations) ? parsed.recommendations : []
37
+ const toAdd = matches.map(m => m[1]).filter(e => !recs.includes(e))
38
+ if (toAdd.length === 0) return { ok: false, action: 'вже є' }
39
+
40
+ parsed.recommendations = [...recs, ...toAdd]
41
+ writeFileSync(extPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8')
42
+ return { ok: true, action: `додано до extensions.json: ${toAdd.join(', ')}` }
43
+ }
44
+ }
45
+ ]