@nitra/cursor 1.9.16 → 1.9.17

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
@@ -4,6 +4,24 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.9.17] - 2026-05-13
8
+
9
+ ### Added
10
+
11
+ - **2 нові rego-полісі для text.mdc VSCode-канону** (мігровано з `check-text.mjs`):
12
+ - `text.vscode_extensions` — `recommendations` має містити три розширення: `DavidAnson.vscode-markdownlint`, `oxc.oxc-vscode`, `timonwong.shellcheck`. Шаблон повідомлень + множина `recommendations_set` (винесена поза `deny`, щоб не порушити `performance/non-loop-expression`).
13
+ - `text.vscode_settings` — `editor.formatOnSave: true` плюс шість мов-блоків (`[javascript]`/`[typescript]`/`[json]`/`[vue]`/`[css]`/`[html]`) з `editor.defaultFormatter: "oxc.oxc-vscode"`. Окремі deny для «не object» і «неправильний defaultFormatter». Канон задає мінімум — додаткові lang-блоки дозволені.
14
+ - **18 нових rego-тестів** (7 для `vscode_extensions` + 11 для `vscode_settings`): happy path, додаткові поля, відсутність кожного розширення, відсутність `formatOnSave`, неправильний defaultFormatter, відсутні lang-блоки. `conftest verify` — **252/252 pass** (+18).
15
+ - **`lint-conftest.mjs` TARGETS — два нові entry для text:** `text.vscode_extensions` (`single: '.vscode/extensions.json'`) і `text.vscode_settings` (`single: '.vscode/settings.json'`), обидва з `rule: 'text'`. Глобально активуються для всіх проєктів з `text` у `.n-cursor.json:rules`.
16
+
17
+ ### Removed
18
+
19
+ - **`check-text.mjs::checkVscodeTextExtensions` / `checkVscodeTextSettings` / `checkVscodeText`** — три JS-функції видалено разом з викликом `await checkVscodeText(pass, fail)` у `check()`. Зміст delegated у rego (`text.vscode_extensions` + `text.vscode_settings`).
20
+
21
+ ### Changed
22
+
23
+ - **`check-text.mjs::checkTextConfigsExistence` — розширено двома записами:** тепер вимагає FS-існування `.vscode/extensions.json` і `.vscode/settings.json` поряд з `.oxfmtrc.json` / `.cspell.json` / `.markdownlint-cli2.jsonc`. lint-conftest з rego skip-ить неіснуючі файли, тому FS-existence лишається в JS — це працює як «єдина точка контролю наявності файлу + delegated content-валідація у rego».
24
+
7
25
  ## [1.9.16] - 2026-05-13
8
26
 
9
27
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.9.16",
3
+ "version": "1.9.17",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,36 @@
1
+ # Перевірка `.vscode/extensions.json` для text (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .vscode/extensions.json -p npm/policy/text/vscode_extensions \
5
+ # --namespace text.vscode_extensions
6
+ #
7
+ # Canonical (text.mdc): у `recommendations` мають бути три розширення
8
+ # - DavidAnson.vscode-markdownlint
9
+ # - oxc.oxc-vscode
10
+ # - timonwong.shellcheck
11
+ #
12
+ # Канон задає мінімум — додаткові записи (від інших правил) дозволені.
13
+ #
14
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
15
+ package text.vscode_extensions
16
+
17
+ import rego.v1
18
+
19
+ required_extensions := {
20
+ "DavidAnson.vscode-markdownlint",
21
+ "oxc.oxc-vscode",
22
+ "timonwong.shellcheck",
23
+ }
24
+
25
+ missing_extension_template := ".vscode/extensions.json: recommendations має містити %q (text.mdc)"
26
+
27
+ # Множина усіх записів `recommendations` (вираз поза deny — щоб regal не лаявся
28
+ # performance/non-loop-expression: інакше `object.get` виконувався б на кожній
29
+ # ітерації по `required_extensions`).
30
+ recommendations_set := {r | some r in object.get(input, "recommendations", [])}
31
+
32
+ deny contains msg if {
33
+ some required in required_extensions
34
+ not required in recommendations_set
35
+ msg := sprintf(missing_extension_template, [required])
36
+ }
@@ -0,0 +1,51 @@
1
+ # Тести для `text.vscode_extensions`. Запуск:
2
+ # conftest verify -p npm/policy/text/vscode_extensions
3
+ package text.vscode_extensions_test
4
+
5
+ import rego.v1
6
+
7
+ import data.text.vscode_extensions
8
+
9
+ canonical := {"recommendations": [
10
+ "DavidAnson.vscode-markdownlint",
11
+ "oxc.oxc-vscode",
12
+ "timonwong.shellcheck",
13
+ ]}
14
+
15
+ test_allow_canonical if {
16
+ count(vscode_extensions.deny) == 0 with input as canonical
17
+ }
18
+
19
+ test_allow_with_additional_extensions if {
20
+ cfg := {"recommendations": [
21
+ "DavidAnson.vscode-markdownlint",
22
+ "oxc.oxc-vscode",
23
+ "timonwong.shellcheck",
24
+ "dbaeumer.vscode-eslint",
25
+ "stylelint.vscode-stylelint",
26
+ ]}
27
+ count(vscode_extensions.deny) == 0 with input as cfg
28
+ }
29
+
30
+ test_deny_missing_markdownlint if {
31
+ cfg := {"recommendations": ["oxc.oxc-vscode", "timonwong.shellcheck"]}
32
+ count(vscode_extensions.deny) > 0 with input as cfg
33
+ }
34
+
35
+ test_deny_missing_oxc if {
36
+ cfg := {"recommendations": ["DavidAnson.vscode-markdownlint", "timonwong.shellcheck"]}
37
+ count(vscode_extensions.deny) > 0 with input as cfg
38
+ }
39
+
40
+ test_deny_missing_shellcheck if {
41
+ cfg := {"recommendations": ["DavidAnson.vscode-markdownlint", "oxc.oxc-vscode"]}
42
+ count(vscode_extensions.deny) > 0 with input as cfg
43
+ }
44
+
45
+ test_deny_empty_recommendations if {
46
+ count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
47
+ }
48
+
49
+ test_deny_no_recommendations_field if {
50
+ count(vscode_extensions.deny) > 0 with input as {}
51
+ }
@@ -0,0 +1,56 @@
1
+ # Перевірка `.vscode/settings.json` для text (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .vscode/settings.json -p npm/policy/text/vscode_settings \
5
+ # --namespace text.vscode_settings
6
+ #
7
+ # Canonical (text.mdc):
8
+ # { "editor.formatOnSave": true,
9
+ # "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
10
+ # "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
11
+ # "[json]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
12
+ # "[vue]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
13
+ # "[css]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
14
+ # "[html]": { "editor.defaultFormatter": "oxc.oxc-vscode" } }
15
+ #
16
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
17
+ package text.vscode_settings
18
+
19
+ import rego.v1
20
+
21
+ language_keys := {"[javascript]", "[typescript]", "[json]", "[vue]", "[css]", "[html]"}
22
+
23
+ # Шаблони повідомлень — через `concat` для regal style/line-length.
24
+ lang_block_not_object_template := concat(" ", [
25
+ ".vscode/settings.json: %q має бути обʼєктом з",
26
+ "\"editor.defaultFormatter\": \"oxc.oxc-vscode\" (text.mdc)",
27
+ ])
28
+
29
+ lang_wrong_formatter_template := concat(" ", [
30
+ ".vscode/settings.json: %q має використовувати",
31
+ "\"oxc.oxc-vscode\" як editor.defaultFormatter (text.mdc)",
32
+ ])
33
+
34
+ # ── deny: editor.formatOnSave ────────────────────────────────────────────
35
+
36
+ deny contains msg if {
37
+ object.get(input, "editor.formatOnSave", null) != true
38
+ msg := ".vscode/settings.json: \"editor.formatOnSave\" має бути true (text.mdc)"
39
+ }
40
+
41
+ # ── deny: [lang].editor.defaultFormatter ────────────────────────────────
42
+
43
+ deny contains msg if {
44
+ some key in language_keys
45
+ block := object.get(input, key, {})
46
+ not is_object(block)
47
+ msg := sprintf(lang_block_not_object_template, [key])
48
+ }
49
+
50
+ deny contains msg if {
51
+ some key in language_keys
52
+ block := object.get(input, key, {})
53
+ is_object(block)
54
+ object.get(block, "editor.defaultFormatter", null) != "oxc.oxc-vscode"
55
+ msg := sprintf(lang_wrong_formatter_template, [key])
56
+ }
@@ -0,0 +1,85 @@
1
+ # Тести для `text.vscode_settings`. Запуск:
2
+ # conftest verify -p npm/policy/text/vscode_settings
3
+ package text.vscode_settings_test
4
+
5
+ import rego.v1
6
+
7
+ import data.text.vscode_settings
8
+
9
+ valid_cfg := {
10
+ "editor.formatOnSave": true,
11
+ "[javascript]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
12
+ "[typescript]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
13
+ "[json]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
14
+ "[vue]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
15
+ "[css]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
16
+ "[html]": {"editor.defaultFormatter": "oxc.oxc-vscode"},
17
+ }
18
+
19
+ # ── happy path ────────────────────────────────────────────────────────────
20
+
21
+ test_allow_canonical if {
22
+ count(vscode_settings.deny) == 0 with input as valid_cfg
23
+ }
24
+
25
+ test_allow_with_additional_lang_block if {
26
+ cfg := json.patch(valid_cfg, [{
27
+ "op": "add",
28
+ "path": "/[python]",
29
+ "value": {"editor.defaultFormatter": "ms-python.python"},
30
+ }])
31
+ count(vscode_settings.deny) == 0 with input as cfg
32
+ }
33
+
34
+ # ── deny: editor.formatOnSave ────────────────────────────────────────────
35
+
36
+ test_deny_format_on_save_false if {
37
+ cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/editor.formatOnSave", "value": false}])
38
+ count(vscode_settings.deny) > 0 with input as cfg
39
+ }
40
+
41
+ test_deny_format_on_save_missing if {
42
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/editor.formatOnSave"}])
43
+ count(vscode_settings.deny) > 0 with input as cfg
44
+ }
45
+
46
+ # ── deny: lang formatters (per-key) ──────────────────────────────────────
47
+
48
+ test_deny_javascript_missing if {
49
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[javascript]"}])
50
+ count(vscode_settings.deny) > 0 with input as cfg
51
+ }
52
+
53
+ test_deny_typescript_wrong_formatter if {
54
+ cfg := json.patch(
55
+ valid_cfg,
56
+ [{"op": "replace", "path": "/[typescript]/editor.defaultFormatter", "value": "prettier"}],
57
+ )
58
+ count(vscode_settings.deny) > 0 with input as cfg
59
+ }
60
+
61
+ test_deny_json_block_not_object if {
62
+ cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/[json]", "value": "oxc.oxc-vscode"}])
63
+ count(vscode_settings.deny) > 0 with input as cfg
64
+ }
65
+
66
+ test_deny_vue_missing_default_formatter if {
67
+ cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/[vue]", "value": {"editor.tabSize": 2}}])
68
+ count(vscode_settings.deny) > 0 with input as cfg
69
+ }
70
+
71
+ test_deny_css_missing if {
72
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[css]"}])
73
+ count(vscode_settings.deny) > 0 with input as cfg
74
+ }
75
+
76
+ test_deny_html_missing if {
77
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[html]"}])
78
+ count(vscode_settings.deny) > 0 with input as cfg
79
+ }
80
+
81
+ # ── deny: empty object ───────────────────────────────────────────────────
82
+
83
+ test_deny_empty_object if {
84
+ count(vscode_settings.deny) > 0 with input as {}
85
+ }
@@ -89,70 +89,11 @@ async function checkV8rIgnore(passFn, failFn) {
89
89
  }
90
90
  }
91
91
 
92
- /**
93
- * Перевіряє VSCode extensions.json для текстового стека.
94
- * @param {(msg: string) => void} passFn callback при успішній перевірці
95
- * @param {(msg: string) => void} failFn callback при помилці
96
- */
97
- async function checkVscodeTextExtensions(passFn, failFn) {
98
- if (!existsSync('.vscode/extensions.json')) {
99
- failFn('.vscode/extensions.json не існує — створи з recommendations згідно n-text.mdc')
100
- return
101
- }
102
- try {
103
- const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
104
- const rec = ext.recommendations
105
- for (const id of ['DavidAnson.vscode-markdownlint', 'oxc.oxc-vscode', 'timonwong.shellcheck']) {
106
- if (Array.isArray(rec) && rec.includes(id)) {
107
- passFn(`extensions.json містить ${id}`)
108
- } else {
109
- failFn(`extensions.json: додай "${id}" у recommendations (див. n-text.mdc)`)
110
- }
111
- }
112
- } catch {
113
- failFn('.vscode/extensions.json — невалідний JSON')
114
- }
115
- }
116
-
117
- /**
118
- * Перевіряє VSCode settings.json для текстового стека.
119
- * @param {(msg: string) => void} passFn callback при успішній перевірці
120
- * @param {(msg: string) => void} failFn callback при помилці
121
- */
122
- async function checkVscodeTextSettings(passFn, failFn) {
123
- if (!existsSync('.vscode/settings.json')) {
124
- failFn('.vscode/settings.json не існує — створи згідно n-text.mdc')
125
- return
126
- }
127
- try {
128
- const settings = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
129
- if (settings['editor.formatOnSave'] === true) {
130
- passFn('settings.json: editor.formatOnSave увімкнено')
131
- } else {
132
- failFn('settings.json: editor.formatOnSave має бути true')
133
- }
134
- for (const t of ['javascript', 'typescript', 'json', 'vue', 'css', 'html']) {
135
- const key = `[${t}]`
136
- if (settings[key]?.['editor.defaultFormatter'] === 'oxc.oxc-vscode') {
137
- passFn(`settings.json: ${key} використовує oxc.oxc-vscode`)
138
- } else {
139
- failFn(`settings.json: ${key} має використовувати oxc.oxc-vscode як defaultFormatter`)
140
- }
141
- }
142
- } catch {
143
- failFn('.vscode/settings.json — невалідний JSON')
144
- }
145
- }
146
-
147
- /**
148
- * Перевіряє VSCode extensions.json та settings.json для текстового стека.
149
- * @param {(msg: string) => void} passFn callback при успішній перевірці
150
- * @param {(msg: string) => void} failFn callback при помилці
151
- */
152
- async function checkVscodeText(passFn, failFn) {
153
- await checkVscodeTextExtensions(passFn, failFn)
154
- await checkVscodeTextSettings(passFn, failFn)
155
- }
92
+ // `.vscode/extensions.json` (`DavidAnson.vscode-markdownlint`, `oxc.oxc-vscode`,
93
+ // `timonwong.shellcheck`) і `.vscode/settings.json` (`editor.formatOnSave` +
94
+ // `[lang].editor.defaultFormatter`) валідують rego-пакети `text.vscode_extensions`
95
+ // і `text.vscode_settings` (зареєстровані глобально у `lint-conftest.mjs` TARGETS
96
+ // з `rule: 'text'`). FS-existence файлів — у `checkTextConfigsExistence`.
156
97
 
157
98
  /**
158
99
  * FS-existence стек текстових конфігів. Контент-валідація — у Rego
@@ -165,7 +106,9 @@ function checkTextConfigsExistence(passFn, failFn) {
165
106
  for (const [path, mdcRef] of [
166
107
  ['.oxfmtrc.json', 'text.oxfmtrc'],
167
108
  ['.cspell.json', 'text.cspell'],
168
- ['.markdownlint-cli2.jsonc', 'text.markdownlint']
109
+ ['.markdownlint-cli2.jsonc', 'text.markdownlint'],
110
+ ['.vscode/extensions.json', 'text.vscode_extensions'],
111
+ ['.vscode/settings.json', 'text.vscode_settings']
169
112
  ]) {
170
113
  if (existsSync(path)) {
171
114
  passFn(`${path} є (структуру перевіряє bun run lint-conftest → ${mdcRef})`)
@@ -247,7 +190,6 @@ export async function check() {
247
190
  const { pass, fail } = reporter
248
191
 
249
192
  await checkV8rIgnore(pass, fail)
250
- await checkVscodeText(pass, fail)
251
193
  await checkTextConfigsExistence(pass, fail)
252
194
 
253
195
  for (const f of ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']) {
@@ -103,6 +103,8 @@ const TARGETS = [
103
103
  { namespace: 'text.cspell', policyDir: 'text', rule: 'text', single: '.cspell.json' },
104
104
  { namespace: 'text.markdownlint', policyDir: 'text', rule: 'text', single: '.markdownlint-cli2.jsonc' },
105
105
  { namespace: 'text.package_json', policyDir: 'text', rule: 'text', single: 'package.json' },
106
+ { namespace: 'text.vscode_extensions', policyDir: 'text', rule: 'text', single: '.vscode/extensions.json' },
107
+ { namespace: 'text.vscode_settings', policyDir: 'text', rule: 'text', single: '.vscode/settings.json' },
106
108
 
107
109
  // ── style-lint ──────────────────────────────────────────────────────────
108
110
  { namespace: 'style_lint.package_json', policyDir: 'style_lint', rule: 'style-lint', single: 'package.json' },