@nitra/cursor 3.26.0 → 3.28.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 +18 -0
- package/bin/n-cursor.js +29 -9
- package/package.json +1 -1
- package/rules/abie/js/applies.mjs +1 -5
- package/rules/abie/js/env_dns.mjs +1 -9
- package/rules/abie/js/firebase_hosting.mjs +1 -5
- package/rules/abie/js/hc_pairing.mjs +1 -8
- package/rules/abie/js/ua_http_route.mjs +1 -10
- package/rules/abie/js/ua_node_selector.mjs +1 -8
- package/rules/adr/js/hooks.mjs +1 -20
- package/rules/bun/js/layout.mjs +1 -19
- package/rules/capacitor/js/platforms.mjs +1 -23
- package/rules/changelog/js/consistency.mjs +1 -29
- package/rules/ci4/js/marksman_config.mjs +1 -19
- package/rules/docker/js/lint.mjs +1 -34
- package/rules/ga/docs/fix.md +16 -149
- package/rules/ga/js/docs/lint.md +12 -93
- package/rules/ga/js/docs/workflows.md +28 -213
- package/rules/ga/js/workflows.mjs +1 -16
- package/rules/ga/lint/docs/lint.md +24 -206
- package/rules/graphql/js/tooling.mjs +1 -9
- package/rules/hasura/js/internal_urls.mjs +1 -24
- package/rules/image-avif/js/avif_generation.mjs +1 -27
- package/rules/image-compress/js/package_setup.mjs +1 -18
- package/rules/js-bun-db/js/safety.mjs +1 -31
- package/rules/js-bun-redis/js/imports.mjs +1 -12
- package/rules/js-lint/js/docs/lint-findings.md +30 -0
- package/rules/js-lint/js/lint-findings.mjs +1 -7
- package/rules/js-lint/js/lint.mjs +1 -10
- package/rules/js-lint/js/tooling.mjs +1 -13
- package/rules/js-lint/js/utils_imports.mjs +1 -18
- package/rules/js-lint-ci/js/lint.mjs +1 -6
- package/rules/js-mssql/js/deps.mjs +1 -10
- package/rules/js-run/js/runtime.mjs +1 -37
- package/rules/js-run/lib/docs/temporal-scan.md +25 -0
- package/rules/k8s/js/manifests.mjs +1 -137
- package/rules/nginx-default-tpl/js/template.mjs +1 -18
- package/rules/npm-module/js/docs/header_doc_pointer.md +25 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +82 -0
- package/rules/npm-module/js/package_structure.mjs +1 -28
- package/rules/npm-module/js/rule_meta.mjs +1 -10
- package/rules/npm-module/js/skill_meta.mjs +1 -13
- package/rules/php/js/tooling.mjs +1 -11
- package/rules/python/js/applies.mjs +1 -8
- package/rules/python/js/tooling.mjs +1 -21
- package/rules/rego/js/applies.mjs +1 -11
- package/rules/rust/js/applies.mjs +1 -7
- package/rules/security/js/sample_secret.mjs +1 -28
- package/rules/security/js/trufflehog.mjs +1 -8
- package/rules/style-lint/js/lint.mjs +1 -5
- package/rules/style-lint/js/tooling.mjs +1 -19
- package/rules/tauri/js/cargo_mutants_config.mjs +1 -20
- package/rules/tauri/js/tooling.mjs +1 -21
- package/rules/test/js/cargo_mutants_config.mjs +1 -12
- package/rules/test/js/location.mjs +1 -9
- package/rules/test/js/no-process-chdir.mjs +1 -21
- package/rules/test/js/no-relative-fs-path.mjs +1 -23
- package/rules/test/js/stryker_config.mjs +4 -25
- package/rules/test/js/vitest-config-pool-forks.mjs +1 -17
- package/rules/text/js/forbidden-prettier.mjs +1 -10
- package/rules/text/js/formatting.mjs +1 -31
- package/rules/vue/js/packages.mjs +1 -24
- package/scripts/docs/coverage-fix-extract.md +32 -0
- package/scripts/docs/lint-cli.md +25 -0
- package/scripts/docs/post-tool-use-fix.md +27 -0
- package/scripts/docs/rename-yaml-extensions.md +36 -0
- package/scripts/docs/skills-cli.md +35 -0
- package/scripts/docs/sync-claude-config.md +52 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +26 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +29 -0
- package/scripts/docs/worktree-cli.md +46 -0
- package/scripts/lib/docs/assert-project-root.md +28 -0
- package/scripts/lib/docs/diff-added-lines.md +34 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +28 -0
- package/scripts/lib/docs/resolve-target-files.md +34 -0
- package/scripts/lib/docs/root-notice.md +28 -0
- package/scripts/lib/docs/rule-meta-helpers.md +34 -0
- package/scripts/lib/docs/rule-meta.md +34 -0
- package/scripts/lib/docs/rule-predicates.md +30 -0
- package/scripts/lib/docs/run-conftest-batch.md +26 -0
- package/scripts/lib/docs/run-lint-step.md +25 -0
- package/scripts/lib/docs/run-rule-cli.md +27 -0
- package/scripts/lib/docs/run-rule.md +32 -0
- package/scripts/lib/docs/run-standard-lint.md +22 -0
- package/scripts/lib/docs/run-standard-rule.md +24 -0
- package/scripts/lib/docs/skill-meta.md +31 -0
- package/scripts/lib/docs/sync-gitignore-worktree.md +31 -0
- package/scripts/lib/docs/template.md +40 -0
- package/scripts/lib/docs/timing-summary.md +24 -0
- package/scripts/lib/docs/workspaces.md +30 -0
- package/scripts/lib/docs/worktree-notice.md +27 -0
- package/scripts/lib/docs/worktree.md +38 -0
- package/scripts/utils/docs/ast-scan-utils.md +50 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +28 -0
- package/scripts/utils/docs/find-package-json-paths.md +26 -0
- package/scripts/utils/docs/lock-cache-dir.md +25 -0
- package/scripts/utils/docs/pass.md +25 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +23 -0
- package/scripts/utils/docs/resolve-cmd.md +29 -0
- package/scripts/utils/docs/resolve-js-root.md +25 -0
- package/scripts/utils/docs/test-helpers.md +36 -0
- package/scripts/utils/docs/walk-cache.md +27 -0
- package/scripts/utils/docs/walkDir.md +32 -0
- package/scripts/utils/docs/with-lock.md +25 -0
- package/scripts/utils/docs/worktree-fingerprint.md +27 -0
- package/skills/docgen/js/docgen-batch.mjs +95 -0
- package/skills/docgen/js/docgen-extract.mjs +33 -18
- package/skills/docgen/js/docgen-gen.mjs +258 -30
- package/skills/docgen/js/docgen-ignore.mjs +4 -7
- package/skills/docgen/js/docgen-prompts.mjs +40 -23
- package/skills/docgen/js/docgen-scan.mjs +1 -8
- package/skills/docgen/js/docs/docgen-extract.md +28 -0
- package/skills/docgen/js/docs/docgen-gen.md +41 -0
- package/skills/docgen/js/docs/docgen-ignore.md +24 -0
- package/skills/docgen/js/docs/docgen-prompts.md +24 -0
- package/skills/docgen/js/docs/docgen-scan.md +48 -0
- package/skills/fix/SKILL.md +5 -31
- package/skills/fix/js/docs/llm-worker.md +27 -0
- package/skills/fix/js/docs/orchestrator.md +32 -0
- package/skills/fix/js/docs/t0.md +29 -0
- package/skills/fix/js/llm-worker.mjs +216 -0
- package/skills/fix/js/orchestrator.mjs +119 -0
- package/skills/fix/js/t0.mjs +213 -0
- package/skills/fix/meta.json +1 -1
- package/skills/start-check/js/check.mjs +1 -16
- package/skills/start-check/js/docs/check.md +34 -0
- package/skills/taze/js/diff.mjs +1 -15
- package/skills/taze/js/docs/diff.md +33 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# docgen-scan.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
`docgen scanner` обходить проєкт, щоб створити JSON-список кодових файлів, розташованих у теці `docs/` поряд із джерелом. Цей список використовується іншими скілами для генерації документації. Інструмент забезпечує детермінований обхід проєкту та відстежує наявність файлів, не використовуючи мережеві ресурси чи LLM.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
- `isSourceFile`: Перевіряє, чи файл є джерелом коду, враховуючи розширення та тестові файли.
|
|
10
|
+
- `docPathForSource`: Генерує шлях до файлу документації для заданого джерела, враховуючи відносні шляхи.
|
|
11
|
+
- `scanForDocgen`: Рекурсивно обходить дерево проєкту, визначаючи кодові файли, які потрібно документувати, та їх відповідні шляхи до документації.
|
|
12
|
+
- `slugForModule`: Генерує унікальний slug для модуля на основі його відносного шляху.
|
|
13
|
+
- `findModuleRoots`: Знаходить абсолютні шляхи коренів модулів проєкту.
|
|
14
|
+
- `nearestModuleRoot`: Знаходить найближчий модуль-предок для заданого файлу.
|
|
15
|
+
- `scanForModules`: Лістить модулі проєкту, визначаючи їхні члени (sourcePath-и) та шляхи до документації.
|
|
16
|
+
- `resolveRoot`: Визначає абсолютний корінь проєкту на основі аргументів командного рядка.
|
|
17
|
+
- `runDocgenScanCli`: Запускає сканування docgen як CLI, виводить JSON-масив у stdout та повертає код виходу.
|
|
18
|
+
- `runDocgenModulesCli`: Запускає сканування модулів docgen як CLI, виводить JSON-масив у stdout та повертає код виходу.
|
|
19
|
+
|
|
20
|
+
## Публічний API
|
|
21
|
+
|
|
22
|
+
- isSourceFile — Визначає, чи є файл кодовим джерелом для створення документації.
|
|
23
|
+
- docPathForSource — Генерує шлях до md-файлу, пов'язаний з кодовим файлом.
|
|
24
|
+
- scanForDocgen — Рекурсивно обстежує структуру проєкту, виявляючи файли для документування.
|
|
25
|
+
- slugForModule — Створює унікальний ідентифікатор (slug) для модуля на основі його шляху.
|
|
26
|
+
- findModuleRoots — Знаходить корені модулів, визначаючи директорії з файлом `package.json`.
|
|
27
|
+
- nearestModuleRoot — Визначає найближчий модуль-предок для заданого файлу.
|
|
28
|
+
- scanForModules — Створює список логічних модулів проєкту, включаючи файли та інформацію про них.
|
|
29
|
+
- resolveRoot — Встановлює базову директорію для сканування, використовуючи аргумент командного рядка або поточну директорію.
|
|
30
|
+
- runDocgenScanCli — Запускає сканування для виявлення файлів документації та виводить результат у форматі JSON.
|
|
31
|
+
- runDocgenModulesCli — Запускає сканування модулів та виводить результат у форматі JSON.
|
|
32
|
+
|
|
33
|
+
## Гарантії поведінки
|
|
34
|
+
|
|
35
|
+
- `isSourceFile` повертає `true`, якщо вказаний шлях до файлу є джерелом коду.
|
|
36
|
+
- `isSourceFile` повертає `false` в іншому випадку.
|
|
37
|
+
- `docPathForSource` повертає шлях до відповідної папки `docs/` для вказаного джерела.
|
|
38
|
+
- `scanForDocgen` повертає JSON-список шляхів до кодових файлів, які потрібно обробити.
|
|
39
|
+
- `scanForDocgen` встановлює прапор `exists` у випадку, якщо файл або папка вже існують.
|
|
40
|
+
- `slugForModule` повертає унікальний URL-шлях для модуля.
|
|
41
|
+
- `findModuleRoots` повертає список кореневих модулів проєкту.
|
|
42
|
+
- `nearestModuleRoot` повертає найближчий кореневий модуль до вказаного шляху.
|
|
43
|
+
- `scanForModules` повертає список модулів проєкту.
|
|
44
|
+
- `resolveRoot` повертає кореневу папку проєкту.
|
|
45
|
+
- `runDocgenScanCli` запускає процес сканування для `docgen`.
|
|
46
|
+
- `runDocgenModulesCli` запускає процес сканування для модулів.
|
|
47
|
+
- У разі невдачі повертається `false` та `null`.
|
|
48
|
+
-
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -8,42 +8,16 @@ description: >-
|
|
|
8
8
|
|
|
9
9
|
## Scope
|
|
10
10
|
|
|
11
|
-
Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor fix` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`).
|
|
11
|
+
Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor fix` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`).
|
|
12
12
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
|
-
1. **Діагностика** — запусти перевірку через retry-обгортку `n_cursor_npx` (визначена у worktree-preflight, крок 0.1: переживає транзитну CDN-гонку щойно опублікованої версії, а реальний `❌` від `fix` віддає одразу). Прапорець `--json` дає **структурований** результат у stdout, щоб не парсити термінальний текст. За замовчуванням — лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `n_cursor_npx fix bun ga --json`:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
n_cursor_npx fix --json
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
2. **Аналіз** — розбери JSON `{ total, failed, rules: [{ ruleId, ok, output }] }`. Працюй **лише** з елементами `ok:false`; їх `output` містить готові `❌`-повідомлення правила (не парси stdout вручну, не визначай правила з тексту). Якщо `failed === 0` — нічого виправляти.
|
|
22
|
-
|
|
23
|
-
3. **Виправлення** — для кожного `❌` відкрий відповідне правило з `.cursor/rules/` і виправ:
|
|
24
|
-
- Створи відсутні конфігураційні файли (`.cspell.json`, `.oxfmtrc.json`, `eslint.config.js`, тощо)
|
|
25
|
-
- Додай відсутні залежності до `package.json`
|
|
26
|
-
- Створи або оновити `.vscode/settings.json` та `extensions.json`
|
|
27
|
-
- Створи відсутні GitHub Actions workflows у `.github/workflows/`
|
|
28
|
-
- Видали заборонені файли та залежності (`package-lock.json`, `yarn.lock`, prettier, тощо)
|
|
29
|
-
- Оновити скрипти в `package.json`
|
|
30
|
-
|
|
31
|
-
4. **Встановлення** — якщо були змінені залежності:
|
|
32
|
-
|
|
33
15
|
```bash
|
|
34
|
-
|
|
16
|
+
n_cursor_npx fix
|
|
35
17
|
```
|
|
36
18
|
|
|
37
|
-
|
|
19
|
+
Exit 0 = чисто, 1 = є unresolved (перевір вивід — буде список правил що не закрились після 3 ітерацій).
|
|
38
20
|
|
|
39
|
-
|
|
40
|
-
oxfmt .
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
6. **Верифікація** — перевір що все виправлено (та сама retry-обгортка `n_cursor_npx`); чекаєш `failed === 0`:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
n_cursor_npx fix --json
|
|
47
|
-
```
|
|
21
|
+
Якщо змінились залежності — `bun i`. Якщо змінились JS/TS файли — `oxfmt .`.
|
|
48
22
|
|
|
49
|
-
|
|
23
|
+
Для конкретних правил: `n_cursor_npx fix bun ga`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# llm-worker.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Цей файл є LLM-робітником, який збирає контекст для n-fix оркестратора, зокрема правила з файлів .mdc та звіти про порушення. Він передає цей контекст великій мовній моделі (LLM) для генерації JSON з пропозиціями змін. Після отримання змін, скрипт застосовує їх до відповідних ресурсів.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
runLlmWorker: Виправляє одне rule-порушення через pi (C1 pattern). Збирає контекст з rule .mdc, файлів з violation output, та передає його в pi для отримання JSON зі змінами. Застосовує зміни, якщо вони були отримані від pi. Якщо pi не повернув JSON, або не зміг застосувати зміни, повертає помилку. Використовує модель Claude HAIKU за замовчуванням, або модель, вказану в опціях.
|
|
10
|
+
|
|
11
|
+
## Публічний API
|
|
12
|
+
|
|
13
|
+
MODEL_HAIKU — Генерує короткі вірші.
|
|
14
|
+
MODEL_SONNET — Генерує вірші у форматі сонету.
|
|
15
|
+
runLlmWorker — Виправляє порушення правил, використовуючи pi (C1 pattern).
|
|
16
|
+
|
|
17
|
+
## Гарантії поведінки
|
|
18
|
+
|
|
19
|
+
- Приймає контекст з файлів `.mdc` та файлів з violation.
|
|
20
|
+
- Повертає JSON з пропозиціями змін.
|
|
21
|
+
- Застосовує зміни, отримані від `pi`.
|
|
22
|
+
- Використовує LLM для генерації змін.
|
|
23
|
+
- Викликає LLM через `pi`.
|
|
24
|
+
- Не використовує tool-use.
|
|
25
|
+
- Не кидає винятків.
|
|
26
|
+
- У разі невдачі повертає `false` та `null`.
|
|
27
|
+
- Не використовує кешування.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# orchestrator.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `convergence-loop` є автономним оркестратором для n-fix, який забезпечує перевірку та виправлення коду без участі LLM. Він ініціює детерміністичні перевірки, regex-парсинг та ітеративні процеси з використанням LLM для вирішення складних проблем. Цей компонент є ключовим елементом системи, що автоматизує процес забезпечення коректності коду n-fix.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
1. **Ініціалізація:** Запускається оркестратор, визначаються максимальна кількість ітерацій (за замовчуванням 3) та порогове значення для ескалації LLM (за замовчуванням 2 невдачі).
|
|
10
|
+
2. **Первинна перевірка:** Виконується детермінована перевірка (T0) на основі заданих правил, без залучення LLM. Результат перевірки записується.
|
|
11
|
+
3. **Автоматичний фікс (T0-auto):** Якщо перевірка виявила порушення, запускається автоматичний фікс, який використовує regex для виявлення та виправлення проблем.
|
|
12
|
+
4. **Ітерація:** Програма повторює наступні кроки до досягнення максимальної кількості ітерацій:
|
|
13
|
+
- **Перевірка (T0):** Виконується детермінована перевірка (T0) на основі поточного стану.
|
|
14
|
+
- **Автоматичний фікс (T0-auto):** Якщо перевірка виявила порушення, запускається автоматичний фікс.
|
|
15
|
+
- **Виклик LLM (T1):** Для кожного порушення, яке не було виправлено автоматичним фіксом, викликається LLM (haiku або sonnet, залежно від кількості попередніх невдач) для генерації рішення.
|
|
16
|
+
- **Оцінка рішення LLM:** LLM генерує рішення, яке оцінюється на предмет успіху.
|
|
17
|
+
- **Оновлення стану:** Якщо рішення LLM успішне, порушення виправляється, і кількість невдалих спроб LLM для цього правила встановлюється на нуль.
|
|
18
|
+
5. **Фінальна перевірка:** Після завершення всіх ітерацій виконується остаточна перевірка, щоб визначити, чи були виправлені всі порушення.
|
|
19
|
+
6. **Повернення результату:** Оркестратор повертає код 0, якщо всі правила були виправлені, і код 1, якщо хоча б одне правило залишилося не виправленим. У випадку помилки парсингу JSON, повертається null.
|
|
20
|
+
|
|
21
|
+
## Гарантії поведінки
|
|
22
|
+
|
|
23
|
+
- Оркестратор запускається при виклику `runOrchestratorCli`.
|
|
24
|
+
- Програма виконує цикл з перевірок, поки не буде досягнуто максимальну кількість ітерацій (`maxIter`).
|
|
25
|
+
- Після кожної ітерації (`T0`, `T0-auto`, `T1`) виконується перевірка.
|
|
26
|
+
- `T0` виконується детерміновано без використання LLM.
|
|
27
|
+
- `T0-auto` виконує regex-парсинг та застосовує програмний фікс у разі виявлення порушення.
|
|
28
|
+
- `T1` використовує LLM через `pi` (ескалація до `haiku` та `sonnet`).
|
|
29
|
+
- `check-gate` повторно запускає `T0` після кожної ітерації.
|
|
30
|
+
- У разі помилки програма повертає `false` або `null`.
|
|
31
|
+
- Програма кешує результати для оптимізації роботи в межах одного запуску.
|
|
32
|
+
- Програма не взаємодіє з мережею.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# t0.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `t0-auto` забезпечує детермінований рівень виправлень для n-fix оркестратора, автоматично застосовуючи програмні фікси на основі аналізу вихідних даних `n-cursor fix --json`. Він парсить повідомлення про порушення, видобуваючи з них інформацію про цільові файли або рядки для вставки, і застосовує відповідні фікси. Це дозволяє швидко та передбачувано виправляти помилки, не використовуючи LLM, і є першим етапом у конвергентному циклі виправлення.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
applyT0Auto: Застосовує всі T0-auto паттерни до вхідного `violationOutput` та повертає об'єкт, що вказує, чи застосовано фікс, та список виконаних дій.
|
|
10
|
+
filterT0AutoRules: Повертає список id правил, для яких є хоч один T0-auto паттерн, на основі `failedRules`.
|
|
11
|
+
runT0AutoCli: Запускає `n-cursor fix --json`, застосовує T0-auto, повторно перевіряє check-gate та виводить підсумок. Повертає 0 у разі успіху, 1 у разі помилки.
|
|
12
|
+
|
|
13
|
+
## Публічний API
|
|
14
|
+
|
|
15
|
+
- applyT0Auto — Застосовує автоматичні правила виправлення до виявлених проблем.
|
|
16
|
+
- filterT0AutoRules — Визначає ідентифікатори правил, які мають T0-auto паттерни.
|
|
17
|
+
- runT0AutoCli — Запускає CLI-інструмент для автоматичного виправлення, включаючи перевірку та звітність.
|
|
18
|
+
|
|
19
|
+
## Гарантії поведінки
|
|
20
|
+
|
|
21
|
+
- `applyT0Auto` застосовує програмний фікс, якщо `violationOutput` містить інформацію, яку можна видобути за допомогою регулярних виразів, і `violation-message` містить цільове значення, що відповідає одному з ідентифікаторів правил T0-auto. Результат: `applied` - boolean, `actions` - string[] (список виконаних дій).
|
|
22
|
+
- `listT0AutoRules` повертає список ідентифікаторів правил T0-auto, які мають хоча б один паттерн.
|
|
23
|
+
- `runT0AutoCli` повертає код виходу 0, якщо процес виконано успішно, і 1, якщо виявлено порушення.
|
|
24
|
+
- T0-auto запускається першим у конвергентному циклі.
|
|
25
|
+
- T1 запускається лише для решти.
|
|
26
|
+
- Система перехоплює помилки та не кидає винятки.
|
|
27
|
+
- В системі немає кешування.
|
|
28
|
+
- Вхідні дані `applyT0Auto` повинні відповідати формату JSON, вихідному від `n-cursor fix --json`.
|
|
29
|
+
- Якщо `violation-message` не містить інформації, яку можна видобути за допомогою регулярних виразів, `applyT0Auto` не виконує жод
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/** @see ./docs/llm-worker.md */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { env } from 'node:process'
|
|
7
|
+
import { CLOUD_MIN, CLOUD_AVG } from '../../../lib/models.mjs'
|
|
8
|
+
|
|
9
|
+
// Тир за замовчуванням: CLOUD_MIN → CLOUD_AVG при ескалації.
|
|
10
|
+
// Перевизначення через N_CURSOR_FIX_MODEL / N_CURSOR_FIX_MODEL_HEAVY.
|
|
11
|
+
export const MODEL = env.N_CURSOR_FIX_MODEL ?? CLOUD_MIN
|
|
12
|
+
export const MODEL_HEAVY = env.N_CURSOR_FIX_MODEL_HEAVY ?? CLOUD_AVG
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Витягує відносні шляхи файлів із violation output.
|
|
16
|
+
* Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} output violation output з fix check
|
|
19
|
+
* @returns {string[]} унікальні відносні шляхи (від кореня проєкту)
|
|
20
|
+
*/
|
|
21
|
+
function extractFilePaths(output) {
|
|
22
|
+
const seen = new Set()
|
|
23
|
+
const results = []
|
|
24
|
+
|
|
25
|
+
// Патерн з workspace: [npm] skills/foo.mjs або [demo] src/bar.ts
|
|
26
|
+
const wsRe = /\[([\w-]+)\]\s+([\w./][\w./\-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
|
|
27
|
+
for (const m of output.matchAll(wsRe)) {
|
|
28
|
+
const p = `${m[1]}/${m[2]}`
|
|
29
|
+
if (!seen.has(p)) {
|
|
30
|
+
seen.add(p)
|
|
31
|
+
results.push(p)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Патерн без workspace: просто path/to/file.ext або ./file.ext
|
|
36
|
+
const re = /(?:^|\s)(\.?[\w][\w./\-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
|
|
37
|
+
for (const m of output.matchAll(re)) {
|
|
38
|
+
const p = m[1]
|
|
39
|
+
if (!seen.has(p)) {
|
|
40
|
+
seen.add(p)
|
|
41
|
+
results.push(p)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return results
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Будує prompt для pi: правило + порушення + поточний вміст файлів.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} ruleId
|
|
52
|
+
* @param {string} ruleMdc вміст .mdc-файлу правила
|
|
53
|
+
* @param {string} output violation output
|
|
54
|
+
* @param {Array<{path:string, content:string}>} files
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function buildPrompt(ruleId, ruleMdc, output, files) {
|
|
58
|
+
const filesBlock =
|
|
59
|
+
files.length === 0
|
|
60
|
+
? '(no files identified)'
|
|
61
|
+
: files.map(f => `<file path="${f.path}">\n${f.content}\n</file>`).join('\n\n')
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
`You fix project structure violations. Return ONLY valid JSON — no explanation, no markdown.`,
|
|
65
|
+
``,
|
|
66
|
+
`Rule (n-${ruleId}.mdc):`,
|
|
67
|
+
`---`,
|
|
68
|
+
ruleMdc,
|
|
69
|
+
`---`,
|
|
70
|
+
``,
|
|
71
|
+
`Violation output:`,
|
|
72
|
+
output,
|
|
73
|
+
``,
|
|
74
|
+
`Current file contents:`,
|
|
75
|
+
filesBlock,
|
|
76
|
+
``,
|
|
77
|
+
`Return JSON with this exact shape:`,
|
|
78
|
+
`{"changes":[{"path":"relative/path/to/file","content":"full corrected file content"}]}`,
|
|
79
|
+
``,
|
|
80
|
+
`Rules:`,
|
|
81
|
+
`- "path" is relative to the project root`,
|
|
82
|
+
`- "content" is the complete new file content (not a diff)`,
|
|
83
|
+
`- Only include files that actually need to change`,
|
|
84
|
+
`- If nothing can be fixed automatically, return {"changes":[],"error":"reason"}`
|
|
85
|
+
].join('\n')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Запускає pi і повертає stdout як рядок.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} prompt
|
|
92
|
+
* @param {string} model
|
|
93
|
+
* @returns {{ text: string, error?: string }}
|
|
94
|
+
*/
|
|
95
|
+
function callPi(prompt, model) {
|
|
96
|
+
const modelArgs = model ? ['--model', model] : []
|
|
97
|
+
const r = spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session', '--mode', 'text', '--no-tools'], {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
timeout: 120_000
|
|
100
|
+
})
|
|
101
|
+
if (r.error) return { text: '', error: r.error.message }
|
|
102
|
+
if (r.status !== 0) {
|
|
103
|
+
const stderr = r.stderr?.slice(0, 300) ?? ''
|
|
104
|
+
if (stderr.toLowerCase().includes('no api key') || stderr.toLowerCase().includes('api key')) {
|
|
105
|
+
const provider = model ? model.split('/')[0] : 'дефолтного провайдера'
|
|
106
|
+
return {
|
|
107
|
+
text: '',
|
|
108
|
+
error: [
|
|
109
|
+
`pi: немає ключа для ${provider}.`,
|
|
110
|
+
`Встановіть N_CLOUD_MIN_MODEL=provider/model-id`,
|
|
111
|
+
`(напр.: openai/gpt-5.4-mini, google/gemini-2.5-flash, ollama/gemma3:4b)`,
|
|
112
|
+
].join(' ')
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { text: '', error: `pi exit ${r.status}: ${stderr}` }
|
|
116
|
+
}
|
|
117
|
+
return { text: r.stdout?.trim() ?? '' }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Парсить JSON-відповідь від pi.
|
|
122
|
+
* pi може обгорнути JSON у ```json ... ```, тому пробуємо витягти.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} text
|
|
125
|
+
* @returns {{ changes: Array<{path:string,content:string}>, error?: string } | null}
|
|
126
|
+
*/
|
|
127
|
+
function parseResponse(text) {
|
|
128
|
+
// Спроба 1: прямий JSON
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(text)
|
|
131
|
+
} catch {
|
|
132
|
+
/* fallthrough */
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Спроба 2: витягти з ```json ... ```
|
|
136
|
+
const m = text.match(/```(?:json)?\s*([\s\S]*?)```/)
|
|
137
|
+
if (m) {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(m[1].trim())
|
|
140
|
+
} catch {
|
|
141
|
+
/* fallthrough */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Спроба 3: перший { ... } блок
|
|
146
|
+
const start = text.indexOf('{')
|
|
147
|
+
const end = text.lastIndexOf('}')
|
|
148
|
+
if (start !== -1 && end > start) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(text.slice(start, end + 1))
|
|
151
|
+
} catch {
|
|
152
|
+
/* fallthrough */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* LLM-worker: виправляє одне rule-порушення через pi (C1 pattern).
|
|
161
|
+
*
|
|
162
|
+
* @param {string} ruleId
|
|
163
|
+
* @param {string} violationOutput output з fix check для цього rule
|
|
164
|
+
* @param {string} projectRoot абсолютний шлях до кореня проєкту
|
|
165
|
+
* @param {{ model?: string }} opts
|
|
166
|
+
* @returns {Promise<{ ok: boolean, error?: string }>}
|
|
167
|
+
*/
|
|
168
|
+
export async function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
169
|
+
const model = opts.model ?? MODEL
|
|
170
|
+
|
|
171
|
+
// 1. Читаємо rule .mdc
|
|
172
|
+
const mdcPath = join(projectRoot, '.cursor', 'rules', `n-${ruleId}.mdc`)
|
|
173
|
+
const ruleMdc = existsSync(mdcPath) ? readFileSync(mdcPath, 'utf8') : '(rule file not found)'
|
|
174
|
+
|
|
175
|
+
// 2. Витягуємо файли з violation output і читаємо їх
|
|
176
|
+
const filePaths = extractFilePaths(violationOutput)
|
|
177
|
+
const files = filePaths
|
|
178
|
+
.map(p => {
|
|
179
|
+
const abs = join(projectRoot, p)
|
|
180
|
+
if (!existsSync(abs)) return null
|
|
181
|
+
try {
|
|
182
|
+
return { path: p, content: readFileSync(abs, 'utf8') }
|
|
183
|
+
} catch {
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
|
|
189
|
+
// 3. Будуємо prompt і викликаємо pi
|
|
190
|
+
const prompt = buildPrompt(ruleId, ruleMdc, violationOutput, files)
|
|
191
|
+
const { text, error: piError } = callPi(prompt, model)
|
|
192
|
+
|
|
193
|
+
if (piError) return { ok: false, error: piError }
|
|
194
|
+
if (!text) return { ok: false, error: 'pi returned empty response' }
|
|
195
|
+
|
|
196
|
+
// 4. Парсимо відповідь
|
|
197
|
+
const parsed = parseResponse(text)
|
|
198
|
+
if (!parsed) return { ok: false, error: `cannot parse pi response: ${text.slice(0, 200)}` }
|
|
199
|
+
if (parsed.error) return { ok: false, error: parsed.error }
|
|
200
|
+
|
|
201
|
+
const changes = parsed.changes ?? []
|
|
202
|
+
if (changes.length === 0) return { ok: false, error: 'pi returned no changes' }
|
|
203
|
+
|
|
204
|
+
// 5. Застосовуємо зміни
|
|
205
|
+
for (const change of changes) {
|
|
206
|
+
if (!change.path || typeof change.content !== 'string') continue
|
|
207
|
+
const abs = join(projectRoot, change.path)
|
|
208
|
+
try {
|
|
209
|
+
writeFileSync(abs, change.content, 'utf8')
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return { ok: false, error: `write ${change.path}: ${e.message}` }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { ok: true }
|
|
216
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** @see ./docs/orchestrator.md */
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
8
|
+
const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MAX_ITER = 3
|
|
11
|
+
const ESCALATE_AFTER = 2
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string[]} args CLI аргументи після 'fix'
|
|
15
|
+
* @param {string} cwd корінь проєкту
|
|
16
|
+
* @returns {Promise<number>} 0 = all clean, 1 = unresolved
|
|
17
|
+
*/
|
|
18
|
+
export async function runOrchestratorCli(args, cwd) {
|
|
19
|
+
const { runLlmWorker, MODEL, MODEL_HEAVY } = await import('./llm-worker.mjs')
|
|
20
|
+
|
|
21
|
+
const maxIterIdx = args.indexOf('--max-iter')
|
|
22
|
+
const maxIter =
|
|
23
|
+
maxIterIdx !== -1 ? Number(args[maxIterIdx + 1] ?? DEFAULT_MAX_ITER) || DEFAULT_MAX_ITER : DEFAULT_MAX_ITER
|
|
24
|
+
const skipIdxs = new Set(maxIterIdx !== -1 ? [maxIterIdx, maxIterIdx + 1] : [])
|
|
25
|
+
const ruleFilter = args.filter((a, i) => !a.startsWith('-') && !skipIdxs.has(i))
|
|
26
|
+
|
|
27
|
+
/** @type {Map<string, number>} ruleId → кількість LLM-провалів підряд */
|
|
28
|
+
const failCount = new Map()
|
|
29
|
+
|
|
30
|
+
// ── Перша перевірка (тихо) ──
|
|
31
|
+
const initial = runFixCheck(cwd, ruleFilter)
|
|
32
|
+
if (!initial) {
|
|
33
|
+
console.error(`❌ fix: помилка перевірки`)
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let failed = initial.rules.filter(r => !r.ok)
|
|
38
|
+
const total = initial.total
|
|
39
|
+
|
|
40
|
+
// Нічого не зламано — коротка відповідь
|
|
41
|
+
if (failed.length === 0) {
|
|
42
|
+
console.log(`✅ fix: ${total} правил — все чисто`)
|
|
43
|
+
return 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Є порушення — показуємо прогрес
|
|
47
|
+
console.log(`🔄 fix: ${failed.length}/${total} порушень (${failed.map(r => r.ruleId).join(', ')})`)
|
|
48
|
+
if (ruleFilter.length) console.log(` filter: ${ruleFilter.join(', ')}`)
|
|
49
|
+
|
|
50
|
+
for (let iter = 1; iter <= maxIter; iter++) {
|
|
51
|
+
// ── T0-auto: детермінований фікс без LLM ──
|
|
52
|
+
spawnSync('bun', [N_CURSOR_BIN, 'fix-t0', ...ruleFilter], { cwd, stdio: 'pipe' })
|
|
53
|
+
|
|
54
|
+
const afterT0 = runFixCheck(cwd, ruleFilter)
|
|
55
|
+
const failedAfterT0 = afterT0?.rules.filter(r => !r.ok) ?? failed
|
|
56
|
+
const t0Fixed = failed.filter(r => !failedAfterT0.find(f => f.ruleId === r.ruleId))
|
|
57
|
+
|
|
58
|
+
if (t0Fixed.length > 0) {
|
|
59
|
+
console.log(` ⚙️ T0-auto: ${t0Fixed.map(r => r.ruleId).join(', ')}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
failed = failedAfterT0
|
|
63
|
+
if (failed.length === 0) break
|
|
64
|
+
|
|
65
|
+
// ── T1: LLM через pi ──
|
|
66
|
+
for (const rule of failed) {
|
|
67
|
+
const prevFails = failCount.get(rule.ruleId) ?? 0
|
|
68
|
+
const model = prevFails >= ESCALATE_AFTER ? MODEL_HEAVY : MODEL
|
|
69
|
+
const label = model || 'pi'
|
|
70
|
+
|
|
71
|
+
const result = await runLlmWorker(rule.ruleId, rule.output, cwd, { model })
|
|
72
|
+
|
|
73
|
+
if (result.ok) {
|
|
74
|
+
console.log(` ⚡ LLM (${label}): ${rule.ruleId}`)
|
|
75
|
+
failCount.delete(rule.ruleId)
|
|
76
|
+
} else {
|
|
77
|
+
failCount.set(rule.ruleId, prevFails + 1)
|
|
78
|
+
const hint = (result.error ?? '').slice(0, 200)
|
|
79
|
+
console.log(` ⚡ LLM (${label}): ${rule.ruleId} ❌ ${hint}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Перевірка після LLM
|
|
84
|
+
const afterLLM = runFixCheck(cwd, ruleFilter)
|
|
85
|
+
failed = afterLLM?.rules.filter(r => !r.ok) ?? failed
|
|
86
|
+
if (failed.length === 0) break
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (failed.length === 0) {
|
|
90
|
+
console.log(`✅ fix: ${total} правил — все чисто`)
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`❌ fix: ${failed.length} невирішених — ${failed.map(r => r.ruleId).join(', ')}`)
|
|
95
|
+
return 1
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Внутрішня check-gate: запускає fix-перевірки і повертає структурований результат.
|
|
100
|
+
* Не є публічним CLI — викликається лише оркестратором.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} cwd
|
|
103
|
+
* @param {string[]} ruleFilter
|
|
104
|
+
* @returns {{ total: number, failed: number, rules: Array<{ ruleId: string, ok: boolean, output: string }> } | null}
|
|
105
|
+
*/
|
|
106
|
+
function runFixCheck(cwd, ruleFilter = []) {
|
|
107
|
+
const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], {
|
|
108
|
+
cwd,
|
|
109
|
+
encoding: 'utf8',
|
|
110
|
+
timeout: 120_000
|
|
111
|
+
})
|
|
112
|
+
const stdout = r.stdout?.trim()
|
|
113
|
+
if (!stdout) return null
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(stdout)
|
|
116
|
+
} catch {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
}
|