@nitra/cursor 1.40.1 → 1.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.41.0] - 2026-05-31
4
+
5
+ ### Changed
6
+
7
+ - js-lint: канон oxlint оновлено — додано 13 правил @e18e/eslint-plugin і ignorePatterns (npm/types/**, demo/node/rules-demo.js)
8
+ - js-lint: мінімальна версія @nitra/eslint-config піднята до 3.10.0 (Rego package_json + js-lint.mdc v1.27)
9
+
3
10
  ## [1.40.1] - 2026-05-31
4
11
 
5
12
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.40.1",
3
+ "version": "1.41.1",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -2,7 +2,7 @@
2
2
  description: Використання pg / mysql2 / Bun SQL у Node.js та Bun
3
3
  globs: "**/package.json,**/src/conn/**"
4
4
  alwaysApply: false
5
- version: '1.12'
5
+ version: '1.14'
6
6
  ---
7
7
 
8
8
  ## Підтримувані версії баз даних
@@ -57,16 +57,12 @@ const sql = format(`MERGE INTO t USING (VALUES %s) AS s(id, date, data) ON ...`,
57
57
  await pgWrite.unsafe(sql)
58
58
 
59
59
  // ✅ UNNEST — 3 параметри незалежно від розміру batch; план стабільний і може кешуватись
60
- const ids = batch.map(r => r.id)
61
- const dates = batch.map(r => r.date)
62
- const data = batch.map(r => JSON.stringify(r.data))
63
-
64
60
  await pgWrite`
65
61
  WITH s(id, date, data) AS (
66
62
  SELECT * FROM unnest(
67
- ${ids}::int[],
68
- ${dates}::date[],
69
- ${data}::jsonb[]
63
+ ${pgWrite.array(batch.map(r => r.id), 'int4')},
64
+ ${pgWrite.array(batch.map(r => r.date), 'date')},
65
+ ${pgWrite.array(batch.map(r => r.data), 'jsonb')}
70
66
  )
71
67
  )
72
68
  MERGE INTO my_table AS t
@@ -98,6 +94,22 @@ await pgWrite`
98
94
  `
99
95
  ```
100
96
 
97
+ ## JSONB-параметри: без `JSON.stringify`
98
+
99
+ Bun SQL серіалізує JS-об'єкти й масиви у JSON автоматично — викликати `JSON.stringify` перед передачею в `::jsonb` / `::jsonb[]` **заборонено**.
100
+
101
+ ```javascript
102
+ // ❌ зайвий JSON.stringify — подвійна серіалізація або зайвий рядок
103
+ await sql`INSERT INTO events (details) VALUES (${JSON.stringify(detailsForEvent)}::jsonb)`
104
+
105
+ await sql`SELECT * FROM unnest(${sql.array(batch.map(r => JSON.stringify(r.data)), 'jsonb')})`
106
+
107
+ // ✅ об'єкт/масив передається напряму
108
+ await sql`INSERT INTO events (details) VALUES (${detailsForEvent}::jsonb)`
109
+
110
+ await sql`SELECT * FROM unnest(${sql.array(batch.map(r => r.data), 'jsonb')})`
111
+ ```
112
+
101
113
  `UNION ALL`-цикл замість `unnest` підходить для малих динамічних запитів (2–5 рядків), де кожна гілка семантично різна. Для bulk upsert — завжди `unnest`.
102
114
 
103
115
  ### Заборонений «drop-in» шим
@@ -5,6 +5,19 @@
5
5
  "categories": {},
6
6
  "rules": {
7
7
  "e18e/prefer-includes": "error",
8
+ "e18e/prefer-array-fill": "deny",
9
+ "e18e/prefer-array-to-reversed": "deny",
10
+ "e18e/prefer-array-to-sorted": "deny",
11
+ "e18e/prefer-array-to-spliced": "deny",
12
+ "e18e/prefer-nullish-coalescing": "deny",
13
+ "e18e/prefer-spread-syntax": "deny",
14
+ "e18e/prefer-url-canparse": "deny",
15
+ "e18e/ban-dependencies": "deny",
16
+ "e18e/prefer-array-from-map": "deny",
17
+ "e18e/prefer-timer-args": "deny",
18
+ "e18e/prefer-static-regex": "deny",
19
+ "e18e/prefer-date-now": "deny",
20
+ "e18e/prefer-regex-test": "deny",
8
21
  "array-callback-return": "deny",
9
22
  "no-new-wrappers": "deny",
10
23
  "import/no-named-as-default": "deny",
@@ -213,7 +213,7 @@ async function checkEslintConfig(passFn, failFn, cwd) {
213
213
  }
214
214
 
215
215
  // Перевірки `prettier` / `@nitra/prettier-config` у залежностях (text.mdc) і
216
- // `@nitra/eslint-config ≥ 3.9.2` тепер у Rego: відповідно
216
+ // `@nitra/eslint-config ≥ 3.10.0` тепер у Rego: відповідно
217
217
  // `npm/policy/text/package_json/` і `npm/policy/js_lint/package_json/`. Тут
218
218
  // лишилася лише workspace-ітерація для `type: "module"` і engines, бо js_lint
219
219
  // Rego запускається лише на кореневому `package.json`.
@@ -2,10 +2,10 @@
2
2
  description: Перевірка JavaScript коду
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
- version: '1.26'
5
+ version: '1.28'
6
6
  ---
7
7
 
8
- **oxlint**, **ESLint**, **jscpd**, **knip**. У скрипті **`lint-js`** і в CI — **`bunx oxlint`**, **`bunx eslint`**, **`bunx jscpd`**, **`bunx knip`** (у CI без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config` мінімум `^3.9.2`** (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint); пакет **`@e18e/eslint-plugin`** окремо не додавай. Пакети oxlint/eslint/jscpd/knip не додавай без потреби монорепо.
8
+ **oxlint**, **ESLint**, **jscpd**, **knip**. У скрипті **`lint-js`** і в CI — **`bunx oxlint`**, **`bunx eslint`**, **`bunx jscpd`**, **`bunx knip`** (у CI без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config`** версія не нижче канонічного мінімуму зі snippet нижче (semver-поріг, єдине джерело істини) (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint); пакет **`@e18e/eslint-plugin`** окремо не додавай. Пакети oxlint/eslint/jscpd/knip не додавай без потреби монорепо.
9
9
 
10
10
  У кожному **`package.json`** проєкту (корінь і всі workspace-пакети) має бути **`"type": "module"`** — весь код у ESM.
11
11
 
@@ -16,12 +16,12 @@ version: '1.26'
16
16
  "lint-js": "bunx oxlint --fix && bunx eslint --fix . && bunx jscpd . && bunx knip --no-config-hints"
17
17
  },
18
18
  "devDependencies": {
19
- "@nitra/eslint-config": "^3.9.4"
19
+ "@nitra/eslint-config": "^3.10.0"
20
20
  }
21
21
  }
22
22
  ```
23
23
 
24
- Канон `type` + `scripts.lint-js` (substring requirement): [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
24
+ Канон `type` + `scripts.lint-js` (substring requirement) і мінімальна `@nitra/eslint-config` (semver-поріг `devDependencies`): [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
25
25
 
26
26
  У `.vscode/extensions.json` `recommendations` мають містити `dbaeumer.vscode-eslint`, `github.vscode-github-actions`, `oxc.oxc-vscode`: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
27
27
 
@@ -2,11 +2,13 @@
2
2
  #
3
3
  # Канон надходить через --data: { "template": { "snippet": ... } }
4
4
  # Структура --data сформована з template/package.json.snippet.json
5
- # (canonical `type` + `scripts.lint-js`).
5
+ # (canonical `type` + `scripts.lint-js` + `devDependencies.@nitra/eslint-config` як мін-поріг).
6
6
  #
7
- # Логіка, що ЛИШАЄТЬСЯ у rego (inverse, не виноситься у template):
8
- # - `engines.node` >= 24, `engines.bun` >= 1.3 (semver range parsing);
9
- # - `@nitra/eslint-config` >= 3.9.2 (semver range parsing).
7
+ # Мінімальна версія `@nitra/eslint-config` ЄДИНЕ джерело в snippet
8
+ # (`devDependencies.@nitra/eslint-config`); rego лише парсить поріг і робить semver-порівняння.
9
+ #
10
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse, без template-значення):
11
+ # - `engines.node` >= 24, `engines.bun` >= 1.3 (semver range parsing).
10
12
  package js_lint.package_json
11
13
 
12
14
  import rego.v1
@@ -44,7 +46,7 @@ deny contains msg if {
44
46
  msg := "package.json: engines.bun має бути >= 1.3 (js-lint.mdc)"
45
47
  }
46
48
 
47
- # ── deny: @nitra/eslint-config >= 3.9.2 (inverse) ───────────────────────
49
+ # ── deny: @nitra/eslint-config >= snippet-поріг ─────────────────────────
48
50
 
49
51
  deny contains msg if {
50
52
  dev := object.get(input, "devDependencies", {})
@@ -56,11 +58,17 @@ deny contains msg if {
56
58
  range := object.get(object.get(input, "devDependencies", {}), "@nitra/eslint-config", "")
57
59
  range != ""
58
60
  not eslint_config_meets_min(range)
59
- msg := sprintf("package.json: @nitra/eslint-config має бути >= 3.9.2 (зараз %q) (js-lint.mdc)", [range])
61
+ msg := sprintf("package.json: @nitra/eslint-config має бути >= %s (зараз %q) (js-lint.mdc)", [eslint_min_display, range])
60
62
  }
61
63
 
62
64
  # ── helpers ──────────────────────────────────────────────────────────────
63
65
 
66
+ # Канонічний мін-поріг `@nitra/eslint-config` із snippet (напр. "^3.10.0").
67
+ eslint_min_range := object.get(object.get(data.template.snippet, "devDependencies", {}), "@nitra/eslint-config", "")
68
+
69
+ # Поріг для повідомлення без діапазонних префіксів (напр. "3.10.0").
70
+ eslint_min_display := trim_left(eslint_min_range, "^~>=v ")
71
+
64
72
  normalize_script(s) := regex.replace(trim_space(s), `\s+`, " ")
65
73
 
66
74
  engines_node_meets(spec) if {
@@ -84,24 +92,25 @@ engines_bun_meets(spec) if {
84
92
  eslint_config_meets_min(range) if startswith(trim_space(range), "workspace:")
85
93
 
86
94
  eslint_config_meets_min(range) if {
87
- parts := split_to_numbers(range)
88
- count(parts) >= 3
89
- parts[0] > 3
95
+ actual := split_to_numbers(range)
96
+ count(actual) >= 3
97
+ min_parts := split_to_numbers(eslint_min_range)
98
+ count(min_parts) >= 3
99
+ semver_gte(actual, min_parts)
90
100
  }
91
101
 
92
- eslint_config_meets_min(range) if {
93
- parts := split_to_numbers(range)
94
- count(parts) >= 3
95
- parts[0] == 3
96
- parts[1] > 9
102
+ # actual >= min_parts за major.minor.patch (лексикографічно).
103
+ semver_gte(a, b) if a[0] > b[0]
104
+
105
+ semver_gte(a, b) if {
106
+ a[0] == b[0]
107
+ a[1] > b[1]
97
108
  }
98
109
 
99
- eslint_config_meets_min(range) if {
100
- parts := split_to_numbers(range)
101
- count(parts) >= 3
102
- parts[0] == 3
103
- parts[1] == 9
104
- parts[2] >= 2
110
+ semver_gte(a, b) if {
111
+ a[0] == b[0]
112
+ a[1] == b[1]
113
+ a[2] >= b[2]
105
114
  }
106
115
 
107
116
  first_major(spec) := major if {
@@ -2,5 +2,8 @@
2
2
  "type": "module",
3
3
  "scripts": {
4
4
  "lint-js": "bunx oxlint --fix && bunx eslint --fix . && bunx jscpd . && bunx knip --no-config-hints"
5
+ },
6
+ "devDependencies": {
7
+ "@nitra/eslint-config": "^3.10.0"
5
8
  }
6
9
  }
@@ -28,11 +28,13 @@
28
28
  },
29
29
  {
30
30
  "name": "n-cursor skill meta",
31
+ "description": "Маніфест skill-пакета n-cursor (npm/skills/<id>/meta.json) — назва, тригери, дозволи",
31
32
  "fileMatch": ["npm/skills/*/meta.json"],
32
33
  "url": "https://unpkg.com/@nitra/cursor/schemas/skill-meta.json"
33
34
  },
34
35
  {
35
36
  "name": "n-cursor rule meta",
37
+ "description": "Маніфест rule-пакета n-cursor (npm/rules/<id>/meta.json) — назва, опис, версія правила",
36
38
  "fileMatch": ["npm/rules/*/meta.json"],
37
39
  "url": "https://unpkg.com/@nitra/cursor/schemas/rule-meta.json"
38
40
  }