@nitra/cursor 1.13.38 → 1.13.40

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,18 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.40] - 2026-05-18
8
+
9
+ ### Fixed
10
+
11
+ - `lint-text` CLI: preflight на `shellcheck`, `patch` і `dotenv-linter` до ланцюжка cspell/shellcheck/dotenv. Канон `lint-text.yml.snippet.yml` — кроки `Install shellcheck` (apt) і `Install dotenv-linter` (curl); rego `text.lint_text`. Bump `text.mdc` `1.28` → `1.29`.
12
+
13
+ ## [1.13.39] - 2026-05-18
14
+
15
+ ### Fixed
16
+
17
+ - `lint-ga` CLI: preflight на `conftest` (поряд із `shellcheck`/`uv`) з install-hint; глобальний `catch` у `bin/n-cursor.js` більше не ковтає повідомлення `failConftestMissing()`. Канон `lint-ga.yml.snippet.yml` — крок `Install conftest` для CI; rego `ga.lint_ga` вимагає curl на release conftest.
18
+
7
19
  ## [1.13.38] - 2026-05-18
8
20
 
9
21
  ### Added
package/bin/n-cursor.js CHANGED
@@ -1405,7 +1405,11 @@ try {
1405
1405
  } catch (error) {
1406
1406
  if (error instanceof ReexecHandoff) {
1407
1407
  process.exitCode = error.code
1408
+ } else if (error instanceof Error && error.message) {
1409
+ console.error(error.message)
1410
+ process.exitCode = 1
1408
1411
  } else {
1412
+ console.error(error)
1409
1413
  process.exitCode = 1
1410
1414
  }
1411
1415
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.38",
3
+ "version": "1.13.40",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,5 +1,6 @@
1
1
  /**
2
- * CLI-обгортка над канонічним `lint-ga` (ga.mdc): робить preflight на `shellcheck` і `uv` (для `uvx`),
2
+ * CLI-обгортка над канонічним `lint-ga` (ga.mdc): робить preflight на `shellcheck`, `uv` (для `uvx`)
3
+ * і `conftest` (для rego-полісі у `check-ga`),
3
4
  * тоді послідовно виконує `bunx github-actionlint`, `uvx zizmor --offline --collect=workflows .` і
4
5
  * делегує до `check-ga.mjs::check()` — там і Rego-частина (через `runConftestBatch`),
5
6
  * і JS cross-file перевірки правил `ga.mdc`.
@@ -17,6 +18,10 @@
17
18
  * `uv` потрібен для `uvx zizmor`. Якщо його нема — `uvx zizmor` падає неінформативно («command not
18
19
  * found»); підказка з командою встановлення коротша й корисніша.
19
20
  *
21
+ * `conftest` потрібен для `check-ga.mjs::runAllGaRego` (`runConftestBatch`). Без preflight крок
22
+ * check-ga кидає виняток, який глобальний `catch` у `bin/n-cursor.js` раніше ковтав без логу —
23
+ * локально це виглядало як мовчазний exit 1.
24
+ *
20
25
  * Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
21
26
  */
22
27
  import { platform } from 'node:process'
@@ -68,6 +73,21 @@ const UV_PREFLIGHT = {
68
73
  successMsg: '✅ uv знайдено в PATH — uvx zizmor запуститься'
69
74
  }
70
75
 
76
+ /** @type {PreflightDep} */
77
+ const CONFTEST_PREFLIGHT = {
78
+ bin: 'conftest',
79
+ winBins: ['conftest.exe'],
80
+ explanation: [
81
+ 'Без нього не запускається пер-документна валідація через rego-полісі (npm/rules/*/policy/)',
82
+ 'у кроці check-ga — `runConftestBatch` завершується hard-fail.'
83
+ ].join('\n '),
84
+ install: [
85
+ 'macOS: brew install conftest',
86
+ 'Universal: https://www.conftest.dev/install/'
87
+ ],
88
+ successMsg: '✅ conftest знайдено в PATH — check-ga виконає rego-полісі через runConftestBatch'
89
+ }
90
+
71
91
  /**
72
92
  * Шукає бінарник у PATH з урахуванням Windows: спершу `winBins`, потім `bin`.
73
93
  * @param {PreflightDep} dep опис залежності
@@ -116,7 +136,7 @@ function preflight(dep) {
116
136
  * Виконує канонічний `lint-ga` з preflight-перевірками і делегує до `check-ga.check()`.
117
137
  *
118
138
  * Послідовність:
119
- * 1) preflight: `shellcheck` (для actionlint SC-правил) і `uv` (для `uvx zizmor`); відсутній → exit 1;
139
+ * 1) preflight: `shellcheck`, `uv` (для `uvx zizmor`) і `conftest` (для check-ga); відсутній → exit 1;
120
140
  * 2) `bunx github-actionlint`;
121
141
  * 3) `uvx zizmor --offline --collect=workflows .`;
122
142
  * 4) `check-ga.mjs::check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file
@@ -132,7 +152,7 @@ function preflight(dep) {
132
152
  */
133
153
  export async function runLintGaCli() {
134
154
  let preflightOk = true
135
- for (const dep of [SHELLCHECK_PREFLIGHT, UV_PREFLIGHT]) {
155
+ for (const dep of [SHELLCHECK_PREFLIGHT, UV_PREFLIGHT, CONFTEST_PREFLIGHT]) {
136
156
  if (!preflight(dep)) preflightOk = false
137
157
  }
138
158
  if (!preflightOk) return 1
@@ -92,6 +92,12 @@ deny contains msg if {
92
92
  msg := sprintf("lint-ga.yml: має бути uses: %s (ga.mdc)", [required_use])
93
93
  }
94
94
 
95
+ deny contains msg if {
96
+ expected_run_blob != ""
97
+ not contains(job_run_blob, "open-policy-agent/conftest")
98
+ msg := "lint-ga.yml: має бути крок Install conftest (ga.mdc)"
99
+ }
100
+
95
101
  deny contains msg if {
96
102
  expected_run_blob != ""
97
103
  not contains(job_run_blob, "bun run lint-ga")
@@ -31,5 +31,11 @@ jobs:
31
31
 
32
32
  - uses: astral-sh/setup-uv@v8.0.0
33
33
 
34
+ - name: Install conftest
35
+ run: >-
36
+ curl -fsSL
37
+ https://github.com/open-policy-agent/conftest/releases/download/v0.62.0/conftest_0.62.0_Linux_x86_64.tar.gz
38
+ | sudo tar -xz -C /usr/local/bin conftest
39
+
34
40
  - name: Lint GA
35
41
  run: bun run lint-ga
@@ -12,7 +12,7 @@
12
12
  * `npm/mdc/text.mdc` (markdown-текст, не JSON/YAML);
13
13
  * - складна валідація скрипта `lint-text` (cspell, markdownlint, v8r у трьох
14
14
  * варіантах, run-shellcheck-text.mjs, обовʼязкові glob-и);
15
- * - workflow `lint-text.yml` має крок `bun run lint-text`.
15
+ * - workflow `lint-text.yml` має крок `bun run lint-text` (структура — rego `text.lint_text`).
16
16
  *
17
17
  * **Що покрила Rego** (`npx \@nitra/cursor check`):
18
18
  * - `npm/policy/text/oxfmtrc/` — обовʼязкові ключі `.oxfmtrc.json` і канонічні
@@ -1,26 +1,134 @@
1
1
  /**
2
- * CLI-обгортка над канонічним `lint-text` (text.mdc): послідовно
2
+ * CLI-обгортка над канонічним `lint-text` (text.mdc): preflight на `shellcheck`, `patch`
3
+ * (для авто-фіксу shellcheck) і `dotenv-linter`; далі послідовно
3
4
  * 1) `cspell .` — перевірка правопису з `@nitra/cspell-dict`;
4
5
  * 2) `runShellcheckText()` — авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
5
6
  * 3) `runDotenvLinter()` — авто-фікс і фінальна перевірка `.env*` через `dotenv-linter`;
6
7
  * 4) `bunx markdownlint-cli2 --fix "**\/*.md" "**\/*.mdc"` — авто-фікс Markdown;
7
- * 5) `runV8rWithGlobs()` — schema-валідація json/json5/yaml/yml/toml через v8r з каталогом `@nitra/cursor`.
8
+ * 5) `runV8rWithGlobs()` — schema-валідація json/json5/yaml/yml/toml через v8r.
9
+ *
10
+ * Без preflight локальний прогін може пройти cspell/markdownlint, а CI на ubuntu-latest
11
+ * (де shellcheck передвстановлений, але dotenv-linter — ні) падає на кроці dotenv-linter
12
+ * з неінформативним повідомленням. Preflight збирає всі відсутні бінарники до першого кроку.
8
13
  *
9
14
  * Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
10
15
  * Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
11
16
  */
17
+ import { platform } from 'node:process'
18
+
12
19
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
20
+ import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
13
21
  import { runDotenvLinter } from './run-dotenv-linter.mjs'
14
22
  import { runShellcheckText } from './run-shellcheck.mjs'
15
23
  import { runV8rWithGlobs } from './run-v8r.mjs'
16
24
 
17
25
  /**
18
- * Виконує канонічний `lint-text`: cspell → run-shellcheck → run-dotenv-linter → markdownlint-cli2 → run-v8r.
19
- * Першу помилку повертаємо як код виходу; наступні кроки не запускаються.
20
- * Усі кроки синхронні (`spawnSync` + sync-ентрі з пакета), тому функція не async.
26
+ * Опис залежності preflight-ом.
27
+ * @typedef {object} PreflightDep
28
+ * @property {string} bin ім'я виконуваного файлу
29
+ * @property {string[]} [winBins] альтернативні імена на Windows
30
+ * @property {string} explanation наслідки відсутності
31
+ * @property {string[]} install команди встановлення
32
+ * @property {string} successMsg повідомлення на pass-шлях
33
+ */
34
+
35
+ /** @type {PreflightDep} */
36
+ const SHELLCHECK_PREFLIGHT = {
37
+ bin: 'shellcheck',
38
+ winBins: ['shellcheck.exe'],
39
+ explanation: [
40
+ 'Без нього `runShellcheckText()` пропускає перевірку tracked `*.sh` — локально lint-text',
41
+ 'може бути зеленим, а CI (shellcheck + patch) падає на тих самих скриптах.'
42
+ ].join('\n '),
43
+ install: [
44
+ 'macOS: brew install shellcheck',
45
+ 'Debian/Ubuntu: sudo apt-get install -y shellcheck',
46
+ 'Arch: sudo pacman -S shellcheck'
47
+ ],
48
+ successMsg: '✅ shellcheck знайдено в PATH — lint-text перевірить *.sh'
49
+ }
50
+
51
+ /** @type {PreflightDep} */
52
+ const PATCH_PREFLIGHT = {
53
+ bin: 'patch',
54
+ explanation: [
55
+ 'Без `patch` не застосуються авто-виправлення shellcheck (`shellcheck -f diff` + `patch -p1`).'
56
+ ].join('\n '),
57
+ install: [
58
+ 'macOS: зазвичай уже є в системі',
59
+ 'Debian/Ubuntu: sudo apt-get install -y patch'
60
+ ],
61
+ successMsg: '✅ patch знайдено в PATH — shellcheck auto-fix працюватиме'
62
+ }
63
+
64
+ /** @type {PreflightDep} */
65
+ const DOTENV_LINTER_PREFLIGHT = {
66
+ bin: 'dotenv-linter',
67
+ winBins: ['dotenv-linter.exe'],
68
+ explanation: [
69
+ 'Без нього не виконається крок `.env*` у lint-text — локально cspell/markdownlint',
70
+ 'пройдуть, а CI без Install dotenv-linter впаде неінформативно.'
71
+ ].join('\n '),
72
+ install: [
73
+ 'macOS: brew install dotenv-linter',
74
+ 'Linux: curl -sSfL https://git.io/JLbXn | sh -s -- -b /usr/local/bin',
75
+ 'cargo: cargo install dotenv-linter'
76
+ ],
77
+ successMsg: '✅ dotenv-linter знайдено в PATH — lint-text перевірить .env*'
78
+ }
79
+
80
+ /**
81
+ * @param {PreflightDep} dep
82
+ * @returns {string | null}
83
+ */
84
+ function resolvePreflightBin(dep) {
85
+ if (platform === 'win32' && dep.winBins) {
86
+ for (const name of dep.winBins) {
87
+ const r = resolveCmd(name)
88
+ if (r) return r
89
+ }
90
+ }
91
+ return resolveCmd(dep.bin)
92
+ }
93
+
94
+ /**
95
+ * @param {PreflightDep} dep
96
+ * @returns {void}
97
+ */
98
+ function printPreflightMissingMessage(dep) {
99
+ console.error(`❌ ${dep.bin} не знайдено в PATH.`)
100
+ console.error(` ${dep.explanation}`)
101
+ console.error(' Встанови:')
102
+ for (const line of dep.install) {
103
+ console.error(` ${line}`)
104
+ }
105
+ console.error(' Деталі: text.mdc → секція про lint-text.')
106
+ }
107
+
108
+ /**
109
+ * @param {PreflightDep} dep
110
+ * @returns {boolean}
111
+ */
112
+ function preflight(dep) {
113
+ if (resolvePreflightBin(dep)) {
114
+ console.log(dep.successMsg)
115
+ return true
116
+ }
117
+ printPreflightMissingMessage(dep)
118
+ return false
119
+ }
120
+
121
+ /**
122
+ * Виконує канонічний `lint-text` з preflight і ланцюжком cspell → shellcheck → dotenv → markdownlint → v8r.
21
123
  * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
22
124
  */
23
125
  export function runLintTextCli() {
126
+ let preflightOk = true
127
+ for (const dep of [SHELLCHECK_PREFLIGHT, PATCH_PREFLIGHT, DOTENV_LINTER_PREFLIGHT]) {
128
+ if (!preflight(dep)) preflightOk = false
129
+ }
130
+ if (!preflightOk) return 1
131
+
24
132
  const cspellCode = runLintStep('cspell', 'npx', ['cspell', '.'])
25
133
  if (cspellCode !== 0) return cspellCode
26
134
 
@@ -0,0 +1,100 @@
1
+ # Перевірка `.github/workflows/lint-text.yml` (text.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/lint-text.yml.snippet.yml.
5
+ # Універсальні workflow-перевірки (checkout, permissions) — у `ga.workflow_common`.
6
+ package text.lint_text
7
+
8
+ import rego.v1
9
+
10
+ expected_name := data.template.snippet.name
11
+
12
+ expected_push_branches := {b | some b in data.template.snippet.on.push.branches}
13
+
14
+ expected_pr_branches := {b | some b in data.template.snippet.on.pull_request.branches}
15
+
16
+ expected_push_paths := {p | some p in data.template.snippet.on.push.paths}
17
+
18
+ expected_runs_on := data.template.snippet.jobs.text["runs-on"]
19
+
20
+ expected_perms := data.template.snippet.jobs.text.permissions
21
+
22
+ job := input.jobs.text
23
+
24
+ job_uses_set contains job.steps[_].uses
25
+
26
+ job_run_blob := concat("\n", [run |
27
+ run := job.steps[_].run
28
+ ])
29
+
30
+ expected_uses_set contains u if {
31
+ some step in data.template.snippet.jobs.text.steps
32
+ u := object.get(step, "uses", "")
33
+ u != ""
34
+ }
35
+
36
+ expected_run_substrings contains r if {
37
+ some step in data.template.snippet.jobs.text.steps
38
+ r := object.get(step, "run", "")
39
+ r != ""
40
+ }
41
+
42
+ deny contains msg if {
43
+ input.name != expected_name
44
+ msg := sprintf("lint-text.yml: name має бути %q (text.mdc)", [expected_name])
45
+ }
46
+
47
+ deny contains msg if {
48
+ not branches_superset_of(input.on.push.branches, expected_push_branches)
49
+ msg := "lint-text.yml: on.push.branches має містити dev і main (text.mdc)"
50
+ }
51
+
52
+ deny contains msg if {
53
+ not branches_superset_of(input.on.pull_request.branches, expected_pr_branches)
54
+ msg := "lint-text.yml: on.pull_request.branches має містити dev і main (text.mdc)"
55
+ }
56
+
57
+ deny contains msg if {
58
+ not paths_superset_of(input.on.push.paths, expected_push_paths)
59
+ msg := "lint-text.yml: on.push.paths має містити очікувані glob-и (text.mdc)"
60
+ }
61
+
62
+ deny contains msg if {
63
+ not job
64
+ msg := "lint-text.yml: jobs.text відсутній (text.mdc)"
65
+ }
66
+
67
+ deny contains msg if {
68
+ job["runs-on"] != expected_runs_on
69
+ msg := sprintf("lint-text.yml: runs-on має бути %s (text.mdc)", [expected_runs_on])
70
+ }
71
+
72
+ deny contains msg if {
73
+ job.permissions.contents != expected_perms.contents
74
+ msg := sprintf("lint-text.yml: permissions.contents має бути %s (text.mdc)", [expected_perms.contents])
75
+ }
76
+
77
+ deny contains msg if {
78
+ count(job.steps) == 0
79
+ msg := "lint-text.yml: jobs.text.steps відсутні (text.mdc)"
80
+ }
81
+
82
+ deny contains msg if {
83
+ some required_use in expected_uses_set
84
+ not required_use in job_uses_set
85
+ msg := sprintf("lint-text.yml: має бути uses: %s (text.mdc)", [required_use])
86
+ }
87
+
88
+ deny contains msg if {
89
+ some required_run in expected_run_substrings
90
+ not contains(job_run_blob, required_run)
91
+ msg := sprintf("lint-text.yml: жоден крок run не містить %q (text.mdc)", [required_run])
92
+ }
93
+
94
+ branches_superset_of(actual, expected) if {
95
+ expected & {b | some b in actual} == expected
96
+ }
97
+
98
+ paths_superset_of(actual, expected) if {
99
+ expected & {p | some p in actual} == expected
100
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".github/workflows/lint-text.yml" }
4
+ }
@@ -0,0 +1,61 @@
1
+ name: Lint Text
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev
7
+ - main
8
+ paths:
9
+ - '**/*.js'
10
+ - '**/*.ts'
11
+ - '**/*.vue'
12
+ - '**/*.html'
13
+ - '**/*.css'
14
+ - '**/*.scss'
15
+ - '**/*.less'
16
+ - '**/*.json'
17
+ - '**/*.jsonc'
18
+ - '**/*.yaml'
19
+ - '**/*.yml'
20
+ - '**/*.toml'
21
+ - '**/*.xml'
22
+ - '**/*.md'
23
+ - '**/*.mdc'
24
+ - '**/*.mdс'
25
+ - '**/*.txt'
26
+ - '**/*.go'
27
+ - '**/*.py'
28
+ - '**/*.php'
29
+ - '**/*.sh'
30
+
31
+ pull_request:
32
+ branches:
33
+ - dev
34
+ - main
35
+
36
+ concurrency:
37
+ group: ${{ github.ref }}-${{ github.workflow }}
38
+ cancel-in-progress: true
39
+
40
+ jobs:
41
+ text:
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ contents: read
45
+ steps:
46
+ - uses: actions/checkout@v6
47
+ with:
48
+ persist-credentials: false
49
+
50
+ - uses: ./.github/actions/setup-bun-deps
51
+
52
+ - name: Install shellcheck
53
+ run: sudo apt-get update && sudo apt-get install -y shellcheck
54
+
55
+ - name: Install dotenv-linter
56
+ run: >-
57
+ curl -sSfL https://git.io/JLbXn
58
+ | sh -s -- -b /usr/local/bin
59
+
60
+ - name: Lint text
61
+ run: bun run lint-text
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Обробка та перевірка текстових файлів, oxfmt, cspell, shellcheck (sh), dotenv-linter (.env*), markdownlint-cli2, v8r, CI
3
3
  alwaysApply: true
4
- version: '1.28'
4
+ version: '1.29'
5
5
  ---
6
6
 
7
7
  **oxfmt** (`.oxfmtrc.json`, редактор), **cspell**, **shellcheck** (tracked `*.sh` у `lint-text`), **[dotenv-linter](https://dotenv-linter.github.io/)** (`.env*` у `lint-text`), **markdownlint-cli2**, **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint** / **timonwong.shellcheck**, workflow **`lint-text`**.
@@ -172,63 +172,9 @@ version: '1.28'
172
172
 
173
173
  Додай workflow `.github/workflows/lint-text.yml`:
174
174
 
175
- ```yaml title=".github/workflows/lint-text.yml"
176
- name: Lint Text
177
-
178
- on:
179
- push:
180
- branches:
181
- - dev
182
- - main
183
- paths:
184
- - '**/*.js'
185
- - '**/*.ts'
186
- - '**/*.vue'
187
- - '**/*.html'
188
- - '**/*.css'
189
- - '**/*.scss'
190
- - '**/*.less'
191
- - '**/*.json'
192
- - '**/*.jsonc'
193
- - '**/*.yaml'
194
- - '**/*.yml'
195
- - '**/*.toml'
196
- - '**/*.xml'
197
- - '**/*.md'
198
- - '**/*.mdc'
199
- - '**/*.mdс'
200
- - '**/*.txt'
201
- - '**/*.go'
202
- - '**/*.py'
203
- - '**/*.php'
204
- - '**/*.sh'
205
-
206
- pull_request:
207
- branches:
208
- - dev
209
- - main
210
-
211
- concurrency:
212
- group: ${{ github.ref }}-${{ github.workflow }}
213
- cancel-in-progress: true
214
-
215
- jobs:
216
- text:
217
- runs-on: ubuntu-latest
218
- permissions:
219
- contents: read
220
- steps:
221
- - uses: actions/checkout@v6
222
- with:
223
- persist-credentials: false
224
-
225
- - uses: ./.github/actions/setup-bun-deps
226
-
227
- - name: Lint text
228
- run: bun run lint-text
229
- ```
175
+ - Канон: [lint-text.yml.snippet.yml](./policy/lint_text/template/lint-text.yml.snippet.yml)
230
176
 
231
- Перед **`./.github/actions/setup-bun-deps`** — **`actions/checkout@v6`** (див. **ga.mdc**). Composite: Node 24, Bun, кеш, `bun install --frozen-lockfile`.
177
+ Перед **`./.github/actions/setup-bun-deps`** — **`actions/checkout@v6`** (див. **ga.mdc**). Після composite — кроки **`Install shellcheck`** (apt) і **`Install dotenv-linter`** (curl), бо `n-cursor lint-text` вимагає обидва бінарники в PATH; на ubuntu-latest shellcheck часто вже є, dotenv-linter — ні. Composite: Node 24, Bun, кеш, `bun install --frozen-lockfile`.
232
178
 
233
179
  Не дублюй окремий workflow з тими самими кроками cspell/markdownlint.
234
180