@nitra/cursor 10.1.0 → 10.2.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 +6 -0
- package/bin/n-cursor.js +8 -15
- package/package.json +1 -1
- package/rules/doc-files/js/docgen-extract.mjs +7 -2
- package/rules/js-lint-ci/js-lint-ci.mdc +2 -2
- package/rules/tauri/tauri.mdc +23 -1
- package/rules/tool-surface/fix.mjs +18 -0
- package/rules/tool-surface/meta.json +1 -0
- package/rules/tool-surface/tool-surface.mdc +72 -0
- package/schemas/rule-meta.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [10.2.0] - 2026-06-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- tool-surface rule — паритет «UI ↔ LLM ↔ оркестратор»: будь-яка дія фронтенду виконувана без UI через спільний каталог тулів (catalog → dispatch → UI/CLI/LLM-адаптери). Платформо-незалежне ядро; per-stack деталі делегуються правилам tauri/vue/capacitor. Авто-активація на фронтенд-залежностях. Додано per-stack секцію «Tool Surface у Tauri» в правило tauri (делегація в Rust-крейт/бінарник, два транспорти, схеми через schemars, дозволи плагінів).
|
|
8
|
+
|
|
3
9
|
## [10.1.0] - 2026-06-15
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/bin/n-cursor.js
CHANGED
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
* n-cursor — CLI завантаження правил та перевірки проєкту
|
|
5
5
|
*
|
|
6
6
|
* Використання:
|
|
7
|
-
* `npx \@nitra/cursor` — завантажити cursor-правила
|
|
8
|
-
*
|
|
9
|
-
* якщо в корені вже є `.n-cursor.json`, спочатку зчитується конфіг і за потреби дописується `$schema`
|
|
10
|
-
* `npx \@nitra/cursor fix bun` — оркестратор лише для вказаних правил; `--json` = check-only (structured output для CI)
|
|
7
|
+
* `npx \@nitra/cursor` — завантажити cursor-правила (синк); якщо в корені вже є `.n-cursor.json`,
|
|
8
|
+
* спочатку зчитується конфіг і за потреби дописується `$schema`
|
|
11
9
|
* `npx \@nitra/cursor rename-yaml-extensions` — k8s `*.yml` → `*.yaml`, `.github` `*.yaml` → `*.yml` (опції: `--dry-run`, `--root=…`; див. bin/rename-yaml-extensions.mjs)
|
|
12
10
|
* `npx \@nitra/cursor post-tool-use-fix` — точка входу PostToolUse hook Claude Code: читає stdin JSON,
|
|
13
11
|
* дістає `tool_input.file_path`, маршрутизує його у відповідні правила
|
|
14
12
|
* (`*.mjs` → `js-lint`, `*.vue` → `js-lint style-lint vue` тощо) і викликає
|
|
15
13
|
* `fix` лише з ними. Прописується автоматично в `.claude/settings.json`.
|
|
16
|
-
* `npx \@nitra/cursor
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* `npx \@nitra/cursor lint` — data-driven оркестратор lint+конформності по `rules/<id>/meta.json` (`lint: per-file|full`):
|
|
15
|
+
* за замовчуванням fix-by-default по дельті vs origin (лише `per-file` правила); `--full` =
|
|
16
|
+
* весь репо (`per-file` ∪ `full`); `--read-only` = без мутацій/LLM (CI); позиційні
|
|
17
|
+
* (не-флаг) аргументи — фільтр правил конформності (мапить колишній `fix <rule>`).
|
|
18
|
+
* CI = `lint --read-only --full` (весь репо, нуль мутацій/LLM).
|
|
20
19
|
* `npx \@nitra/cursor lint-ga` — канонічний lint-ga (ga.mdc): preflight на `shellcheck` →
|
|
21
20
|
* `bunx github-actionlint` → `uvx zizmor --offline --collect=workflows .`
|
|
22
21
|
* `npx \@nitra/cursor lint-rego` — канонічний lint-rego (conftest.mdc + rego.mdc):
|
|
@@ -1542,12 +1541,6 @@ try {
|
|
|
1542
1541
|
|
|
1543
1542
|
break
|
|
1544
1543
|
}
|
|
1545
|
-
case 'lint-ci': {
|
|
1546
|
-
// CI = весь репо в read-only (нуль мутацій, нуль LLM) — еквівалент `lint --read-only --full`.
|
|
1547
|
-
process.exitCode = await runLint({ full: true, readOnly: true })
|
|
1548
|
-
|
|
1549
|
-
break
|
|
1550
|
-
}
|
|
1551
1544
|
case 'lint-ga': {
|
|
1552
1545
|
// Канонічний lint-ga з preflight на shellcheck → actionlint → zizmor → check-ga (ga.mdc).
|
|
1553
1546
|
// Останній крок (check-ga) async — тому await обов'язковий, інакше process.exitCode буде Promise.
|
|
@@ -1727,7 +1720,7 @@ try {
|
|
|
1727
1720
|
default: {
|
|
1728
1721
|
console.error(`❌ Невідома команда: ${command}`)
|
|
1729
1722
|
console.error(
|
|
1730
|
-
` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, taze, start-check, change, release, skill, worktree,
|
|
1723
|
+
` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, taze, start-check, change, release, skill, worktree, trace, doc-files, doc-aggregate`
|
|
1731
1724
|
)
|
|
1732
1725
|
process.exitCode = 1
|
|
1733
1726
|
}
|
package/package.json
CHANGED
|
@@ -42,8 +42,13 @@ const IMPORT_AS_RE = /[ \t]{1,8}as[ \t]{1,8}.{0,200}/
|
|
|
42
42
|
const WRITE_FS_RE = /\b(writeFile|mkdir|rmdir|unlink|appendFile|createWriteStream|rm\()/
|
|
43
43
|
const CATCH_RE = /catch\s*\(/
|
|
44
44
|
const TRY_RE = /\btry\s*\{/
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Falsy-return як «fail-safe» — лише коли воно в catch/error-гілці (інакше це
|
|
46
|
+
// звичайний guard `if (!x) return null`, не обробка помилки). Уникає over-claim.
|
|
47
|
+
const FALSY_RETURN_RE = /catch[\s\S]{0,400}?return\s+(false|null|''|"")/
|
|
48
|
+
// Мережа: окрім явного fetch/http, ловимо абстраговані клієнти (graphql/db/rpc/
|
|
49
|
+
// octokit/.request/.query). Хибний false-negative тут = небезпечна гарантія
|
|
50
|
+
// «без мережі», тож свідомо схиляємось до over-detection (м'якший бік помилки).
|
|
51
|
+
const NETWORK_RE = /\bfetch\(|https?:\/\/|\bhttps?\.|axios|\bgot\(|graphql|\.request\(|\.query\(|\.mutate\(|octokit|node-fetch|undici|\bgrpc\b|websocket/i
|
|
47
52
|
// Кеш — лише за ІМЕНОВАНИМ маркером (`cache`/`Cache`/`memoize`), не за будь-яким
|
|
48
53
|
// `new Map()`: акумулятор (напр. `byPath = new Map()`) — не кеш, а хибна гарантія
|
|
49
54
|
// «Кешує результати» гірша за пропуск (фабрикація > мовчання).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Крос-файловий ci-етап js-lint — jscpd (детектор клонів) і knip (невикористані експорти). Лише у lint
|
|
2
|
+
description: Крос-файловий ci-етап js-lint — jscpd (детектор клонів) і knip (невикористані експорти). Лише у `lint --full`, по всьому репо.
|
|
3
3
|
globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
version: '1.0'
|
|
@@ -8,7 +8,7 @@ version: '1.0'
|
|
|
8
8
|
# js-lint-ci — крос-файловий ci-етап
|
|
9
9
|
|
|
10
10
|
`jscpd` і `knip` аналізують увесь граф проєкту, тож мають сенс лише у повному прогоні
|
|
11
|
-
`npx @nitra/cursor lint-
|
|
11
|
+
`npx @nitra/cursor lint --full` (CI: `lint --read-only --full`) — не у швидкому `lint` по змінених файлах. Per-file режиму нема.
|
|
12
12
|
|
|
13
13
|
Швидкий етап js-lint (oxlint/eslint) — у правилі `js-lint` (`lint: per-file`).
|
|
14
14
|
|
package/rules/tauri/tauri.mdc
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Tauri
|
|
3
3
|
globs: "**/src-tauri/**,**/tauri.conf.json"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
version: '1.
|
|
5
|
+
version: '1.5'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
У `.vscode/extensions.json` `recommendations` має містити `tauri-apps.tauri-vscode`:
|
|
@@ -66,3 +66,25 @@ exclude_globs = [
|
|
|
66
66
|
- якщо обидва канонічні ключі (`additional_cargo_test_args`, `exclude_globs`) вже присутні — `manual cargo-mutants config preserved`, нічого не зміниться;
|
|
67
67
|
- якщо якийсь канонічний ключ відсутній — додається окремим блоком у кінці файла, без зміни існуючих значень.
|
|
68
68
|
- Послідовний `fix test` → `fix tauri` створює Tauri-config; повторний `fix tauri` не дублює секцій; повторний `fix test` не перетирає Tauri-tuning.
|
|
69
|
+
|
|
70
|
+
## Tool Surface у Tauri (реалізація ядра `tool-surface`)
|
|
71
|
+
|
|
72
|
+
Це per-stack реалізація правила **`tool-surface`** (`n-tool-surface`) для Tauri+Rust. Контракт (каталог → `dispatch` → UI/оркестратор/LLM, інваріант паритету, конверт) — у тому правилі; тут — як він лягає на Tauri.
|
|
73
|
+
|
|
74
|
+
**Реальна робота живе в Rust, JS-каталог — тонкий call surface.** Handler тула не містить логіки сам — він **делегує** в native. Одна реалізація в Rust-крейті backs два споживачі:
|
|
75
|
+
|
|
76
|
+
- **Бінарник** (`src-tauri` як CLI, або окремий crate-bin) — headless-вхід для оркестратора;
|
|
77
|
+
- **Tauri-команда** (`#[tauri::command]`) — той самий крейт-fn, обгорнутий для UI.
|
|
78
|
+
|
|
79
|
+
**Два транспорти одного каталогу** (`src/tool/transports`):
|
|
80
|
+
|
|
81
|
+
- **UI (in-app):** `invoke(tool.tauri, input)` → Tauri-команда → крейт. Ключі `input` мапляться 1:1 на аргументи команди (camelCase, напр. `tasksDir`); поля вкладених struct лишаються snake_case (Tauri конвертить лише імена top-level аргументів).
|
|
82
|
+
- **Оркестратор (headless):** `bin/<app>.mjs` спавнить зібраний бінарник (`<bin> <verb> …` per-verb, або уніфікований `<bin> exec '<json>'`), парсить JSON stdout.
|
|
83
|
+
|
|
84
|
+
**Конверт:** Tauri-команда повертає `Result<T, String>`; адаптер мапить у `{ ok, output }` / `{ ok:false, error }`. Бінарник друкує конверт у stdout, exit ≠ 0 на `ok:false`.
|
|
85
|
+
|
|
86
|
+
**Єдине джерело схем:** надавай перевагу `schemars`-derive на Rust-param-структурах → бінарник віддає маніфест (`<bin> schema`); або тримай схему в JS-каталозі й валідуй до `invoke`. **Не дублюй** контракт між Rust і JS — лише деривація + спільні тест-вектори.
|
|
87
|
+
|
|
88
|
+
**Дозволи:** будь-який плагін, який смикають тули (`tauri-plugin-dialog`, `tauri-plugin-http` для локальної LLM тощо), має бути в `src-tauri/capabilities/*.json` і зареєстрований у `lib.rs` — інакше виклик тихо падає.
|
|
89
|
+
|
|
90
|
+
**LLM-раннер in-app:** chat-loop ходить до OpenAI-сумісного ендпоінта через `tauri-plugin-http` fetch (бо webview-fetch обмежений CSP/capability), а тули виконує через спільний `dispatch` (той самий, що й UI).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
|
|
2
|
+
import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
6
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
7
|
+
* @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
8
|
+
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
9
|
+
*/
|
|
10
|
+
export function run(ctx) {
|
|
11
|
+
return runStandardRule(import.meta.dirname, ctx)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
|
+
process.exitCode = await runRuleCli(import.meta.dirname)
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "auto": { "predicate": "depInAnyPackageJson", "arg": ["vue", "react", "svelte", "@angular/core", "preact", "solid-js", "@tauri-apps/api", "@capacitor/core"] } }
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Tool Surface — будь-яка дія фронтенду має бути виконуваною без UI (CLI + LLM) через спільний каталог тулів; UI/оркестратор/LLM — рівноправні адаптери
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '1.0'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tool Surface — паритет «UI ↔ LLM ↔ оркестратор»
|
|
8
|
+
|
|
9
|
+
## Принцип
|
|
10
|
+
|
|
11
|
+
**Будь-яка дія, яку людина виконує через фронтенд, мусить бути виконуваною як `tool` — без UI — бо вона організована як іменований виклик зі схемою, до якого однаково дотягуються UI, скриптовий оркестратор і LLM.**
|
|
12
|
+
|
|
13
|
+
Ключовий новий споживач — **LLM**, тож одиниця називається `tool`. Фронтенд — лише один з адаптерів, а не єдині двері.
|
|
14
|
+
|
|
15
|
+
## Це НЕ про «винести логіку на бекенд»
|
|
16
|
+
|
|
17
|
+
Лінія поділу не «фронт ↔ бек», а:
|
|
18
|
+
|
|
19
|
+
> виклик, досяжний **лише через UI-взаємодію** → погано
|
|
20
|
+
> виклик, досяжний як **іменований tool зі схемою** → добре
|
|
21
|
+
|
|
22
|
+
JS-логіка може лишатися на фронті — її лише треба **витягнути** з обробника події в окремий tool (ім'я + параметри + схема), який однаково кличе UI, оркестратор і LLM. Tool Surface — це **call surface**, не обов'язково бекенд.
|
|
23
|
+
|
|
24
|
+
## Три шари
|
|
25
|
+
|
|
26
|
+
1. **Tool Catalog** (`src/tool/`) — декларативний каталог. Кожен tool: `name`, `summary`, схема входу/виходу, `handler`. Handler **може бути фронтендовою JS-функцією** (або делегувати в native/HTTP/DB). Це **єдине джерело правди**; з нього генеруються schema-маніфест (для LLM) і типи клієнта.
|
|
27
|
+
2. **Dispatch** — одна функція `dispatch(name, input) → { ok, output } | { ok: false, error }`: валідує вхід за схемою, кличе handler, повертає **уніфікований конверт**. Не прив'язана до UI-рендеру/подій.
|
|
28
|
+
3. **Споживачі** (усі обов'язкові):
|
|
29
|
+
- **UI** — компонент кличе `dispatch`, без inline-логіки дії.
|
|
30
|
+
- **Оркестратор** — машинний вхід `<bin> <tool> '<json>'` (+ `schema`/`list`). Кличуть скрипти, не люди — людський CLI з verb-ами не потрібен.
|
|
31
|
+
- **LLM** — tool-маніфест із каталогу; tool_call маршрутизується в `dispatch`.
|
|
32
|
+
|
|
33
|
+
## Інваріант паритету (серце правила)
|
|
34
|
+
|
|
35
|
+
- **Жодної дії, досяжної лише через UI-взаємодію.** Обробник події **делегує** в `dispatch`/каталог, а не містить inline-логіку (мережа, мутація моделі, виклик бекенду).
|
|
36
|
+
- Дозволено: логіка у фронтенд-коді. Заборонено: логіка **зашита** в обробник кліку/сабміту так, що дотягтись можна лише кліком.
|
|
37
|
+
- Кожен tool каталогу має **усіх** споживачів (UI + оркестратор + LLM), інакше це не headless.
|
|
38
|
+
|
|
39
|
+
## Єдине джерело схем
|
|
40
|
+
|
|
41
|
+
Схема входу tool-а живе в каталозі (zod / JSON Schema; для Rust — `schemars`). З неї генеруються: tool-маніфест для LLM (формат залежить від провайдера — OpenAI function-calling, Anthropic tools або MCP), CLI-довідка, типи клієнта. Тул-визначення **не повинні розходитися** з каталогом — лише деривація, не дублювання.
|
|
42
|
+
|
|
43
|
+
## Уніфікований конверт
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{ "ok": true, "output": { /* результат */ } }
|
|
47
|
+
{ "ok": false, "error": { "code": "validation|not_found|io|…", "message": "…" } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Оркестратор: `ok:false` → ненульовий exit. UI/LLM: `Result` / tool_result з `is_error`.
|
|
51
|
+
|
|
52
|
+
## Дворівнева структура (архітектура спільна, реалізація per-stack)
|
|
53
|
+
|
|
54
|
+
Архітектура **спільна**, реалізація **навмисно розходиться** за стеком. Це правило тримає платформо-незалежне **ядро**; конкретику делегують профільні правила:
|
|
55
|
+
|
|
56
|
+
| Рівень | Що тут | Де |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| **Ядро** | принцип, інваріант паритету, контракт каталог/`dispatch`/схема/конверт, 3 споживачі | це правило |
|
|
59
|
+
| **Tauri+Rust** | handler делегує в Rust-крейт/бінарник; машинний bin; `invoke`/spawn | `n-tauri` |
|
|
60
|
+
| **Capacitor / pure-web** | handler — JS напряму; bin = node/bun-скрипт, що імпортує handlers | `n-capacitor` |
|
|
61
|
+
| **UI-адаптер** | компонент делегує в `dispatch`, нуль inline-логіки | `n-vue` |
|
|
62
|
+
|
|
63
|
+
## Конвенція файлів
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
src/tool/
|
|
67
|
+
catalog.(js|ts) ← каталог тулів (single source): name, summary, input schema, mapping
|
|
68
|
+
dispatch.(js|ts) ← dispatch(name, input) + валідація + конверт
|
|
69
|
+
manifest.(js|ts) ← каталог → LLM tools ; CLI help
|
|
70
|
+
transports.(js|ts) ← UI-транспорт (invoke/fetch) ; CLI-транспорт (spawn/import)
|
|
71
|
+
bin/<app>.mjs ← машинний вхід: <tool> '<json>' | schema | list
|
|
72
|
+
```
|
package/schemas/rule-meta.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"properties": {
|
|
9
9
|
"lint": {
|
|
10
10
|
"type": "string",
|
|
11
|
-
"enum": ["
|
|
12
|
-
"description": "
|
|
11
|
+
"enum": ["per-file", "full"],
|
|
12
|
+
"description": "Scope lint-кроку: per-file (декомпозиція по змінених файлах, дельта vs origin) або full (нероздільно крос-файловий, лише `lint --full`)."
|
|
13
13
|
},
|
|
14
14
|
"auto": {
|
|
15
15
|
"description": "Умова автоактивації правила: \"завжди\", масив id правил-залежностей, glob, або іменований предикат.",
|