@nitra/cursor 1.13.2 → 1.13.8
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 +59 -0
- package/bin/n-cursor.js +4 -2
- package/package.json +4 -2
- package/rules/ga/fix/workflows/check.mjs +6 -109
- package/rules/ga/policy/package_json/package_json.rego +24 -0
- package/rules/ga/policy/package_json/target.json +8 -0
- package/rules/ga/policy/vscode_extensions/target.json +8 -0
- package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +16 -0
- package/rules/ga/policy/vscode_settings/target.json +8 -0
- package/rules/ga/policy/vscode_settings/vscode_settings.rego +24 -0
- package/rules/ga/policy/zizmor_yml/target.json +8 -0
- package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +17 -0
- package/rules/js-lint/fix/tooling/check.mjs +6 -83
- package/rules/js-lint/policy/jscpd/jscpd.rego +38 -0
- package/rules/js-lint/policy/jscpd/target.json +8 -0
- package/rules/js-lint/policy/vscode_extensions/target.json +8 -0
- package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +25 -0
- package/rules/security/fix/gitleaks/check.mjs +8 -45
- package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +12 -0
- package/rules/security/policy/gitleaks/gitleaks.rego +17 -0
- package/rules/security/policy/gitleaks/target.json +8 -0
- package/rules/security/policy/package_json/package_json.rego +22 -59
- package/rules/security/policy/package_json/template/package.json.contains.json +1 -0
- package/rules/security/policy/package_json/template/package.json.deny.json +4 -0
- package/rules/security/policy/package_json/template/package.json.snippet.json +1 -0
- package/rules/security/security.mdc +7 -26
- package/rules/vue/fix/packages/check.mjs +7 -64
- package/rules/vue/policy/package_json/package_json.rego +45 -2
- package/rules/vue/vue.mdc +15 -2
- package/scripts/ensure-nitra-cursor-dev-dependencies.mjs +41 -21
- package/scripts/utils/check-mdc-template-refs.mjs +47 -0
- package/scripts/utils/inline-template-links.mjs +60 -0
- package/scripts/utils/run-conftest-batch.mjs +60 -33
- package/scripts/utils/run-rule.mjs +16 -1
- package/scripts/utils/template.mjs +215 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,61 @@
|
|
|
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.8] - 2026-05-17
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Перенесено частину per-document логіки з `fix` у Rego policy:
|
|
12
|
+
- `js-lint`: `.jscpd.json` і `.vscode/extensions.json`;
|
|
13
|
+
- `ga`: `package.json#scripts.lint-ga`, `.vscode/extensions.json`, `.vscode/settings.json`, `.github/zizmor.yml`;
|
|
14
|
+
- `security`: `.gitleaks.toml` (`[extend].useDefault = true`);
|
|
15
|
+
- `vue`: залежності Vue/Vite-пакетів і заборону `esbuild`.
|
|
16
|
+
- Відповідні JS check-и спрощено до FS/cross-file/AST/tooling частини без дублювання Rego-умов.
|
|
17
|
+
- `ensureNitraCursorInRootDevDependencies` тепер додає `@nitra/cursor` тільки в `package.json` поруч із запуском, якщо в ньому є `workspaces`.
|
|
18
|
+
- `vue.mdc` уточнює тестування через Bun Test Runner + Vue Test Utils/happy-dom замість Vitest/jsdom.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `npm/package.json#devDependencies` — прибрано self-reference `@nitra/cursor`, щоб published package знову відповідав `npm-module` compact-package canon.
|
|
23
|
+
|
|
24
|
+
## [1.13.7] - 2026-05-17
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `inlineTemplateLinks`: `String.replace(needle, replacement)` інтерпретує `$'`, `$&` тощо у `replacement`. Через це інлайнінг `.gitleaks.toml.snippet.toml` (де є `$'''`) ламав вивід — хвіст `.mdc` реінжектився всередину блока. Перехід на function-replacer (`(_) => replacement`) усуває це. Додано регресійний тест із фікстурою `with-dollar.toml`.
|
|
29
|
+
|
|
30
|
+
## [1.13.6] - 2026-05-17
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `npm/scripts/utils/inline-template-links.mjs` — `inlineTemplateLinks(text, ruleDir)`: під час sync знаходить markdown-лінки виду `[label](./…/template/…)` у `.mdc` і замінює їх inline fenced-блоком з вмістом відповідного файла. Відсутній файл — hard error (fail loud).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `readBundledRuleContent` у `npm/bin/n-cursor.js` тепер пропускає текст правила через `inlineTemplateLinks` перед записом у `.cursor/rules/n-*.mdc`. Template-посилання у скопійованих правилах більше не зламані.
|
|
39
|
+
|
|
40
|
+
## [1.13.5] - 2026-05-17
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Оркестратор `run-rule.mjs` тепер викликає `findMissingMdcRefs` для кожного правила — fail, якщо файл у `template/` не згаданий як markdown-посилання у `<id>.mdc`. Поки що активно лише для `security` (єдине правило з `template/`); готова страховка для Phase 2+.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- `check-mdc-template-refs.test.mjs` тест 3 — фіксував дубль test 1; тепер використовує окрему `no-templates` фікстуру, що дійсно валідує "no template/ dirs → empty result".
|
|
49
|
+
|
|
50
|
+
## [1.13.4] - 2026-05-17
|
|
51
|
+
|
|
52
|
+
### Removed
|
|
53
|
+
|
|
54
|
+
- `npm/package.json#devDependencies` — повторно видалено self-reference `@nitra/cursor` (порушує canon `npm-module`: «devDependencies не публікуються користувачам пакета»). Автоматично повертався у попередніх тасках template-dir роботи; цей коміт остаточно прибирає.
|
|
55
|
+
|
|
56
|
+
## [1.13.3] - 2026-05-17
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- `security/security.mdc` — прибрано inline merge-фрагменти (package.json snippet для `lint-security`, .gitleaks.toml повний канон), замість них markdown-посилання на файли в `template/` (single source of truth). Зміст правила залишається (описи для чого потрібен gitleaks, GitHub Actions), видалено дублювання фіксованого коду.
|
|
61
|
+
|
|
7
62
|
## [1.13.2] - 2026-05-17
|
|
8
63
|
|
|
9
64
|
### Changed
|
|
@@ -20,6 +75,10 @@
|
|
|
20
75
|
|
|
21
76
|
### Changed
|
|
22
77
|
|
|
78
|
+
- `security/fix/gitleaks/check.mjs` читає канон з `template/`, не з inline regex.
|
|
79
|
+
- `security/policy/package_json/package_json.rego` читає очікувані значення з `data.template.*`, не з inline literals.
|
|
80
|
+
- Оркестратор `run-rule.mjs` для policy-концернів вантажить `template/` через `resolveConcernTemplateData` і передає у `runConftestBatch.templateData`.
|
|
81
|
+
- Снепет `.gitleaks.toml.snippet.toml` тримає канонічний title + allowlist paths (description лишається user-specific).
|
|
23
82
|
- **9 правил переведено з `alwaysApply: true` на `alwaysApply: false` + `globs:`** — AI-контекст у Cursor/Claude Code підвантажується лише при роботі з релевантними файлами; програмна валідація через `npx check <rule>` залишається повністю функціональною незалежно від AI-контексту. Економить контекстне вікно у сесіях, де редагують код, далекий від відповідних конфігів.
|
|
24
83
|
- **`bun`** (`1.7 → 1.8`) — `globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"`
|
|
25
84
|
- **`capacitor`** (`1.0 → 1.1`) — `globs: "**/capacitor.config.json,**/android/**,**/ios/**"`
|
package/bin/n-cursor.js
CHANGED
|
@@ -67,6 +67,7 @@ import { cwd, env } from 'node:process'
|
|
|
67
67
|
import { fileURLToPath } from 'node:url'
|
|
68
68
|
|
|
69
69
|
import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
|
|
70
|
+
import { inlineTemplateLinks } from '../scripts/utils/inline-template-links.mjs'
|
|
70
71
|
import {
|
|
71
72
|
detectAutoRules,
|
|
72
73
|
detectLegacyRuleIds,
|
|
@@ -394,7 +395,7 @@ function normalizeRuleName(ruleName) {
|
|
|
394
395
|
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
|
|
395
396
|
* @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
|
|
396
397
|
*/
|
|
397
|
-
function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
398
|
+
async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
398
399
|
const id = normalizeRuleName(rule)
|
|
399
400
|
const bundledPath = join(bundledRulesDir, id, `${id}.mdc`)
|
|
400
401
|
if (!existsSync(bundledPath)) {
|
|
@@ -402,7 +403,8 @@ function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
|
402
403
|
`Немає файлу ${id}/${id}.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
|
|
403
404
|
)
|
|
404
405
|
}
|
|
405
|
-
|
|
406
|
+
const text = await readFile(bundledPath, 'utf8')
|
|
407
|
+
return inlineTemplateLinks(text, dirname(bundledPath))
|
|
406
408
|
}
|
|
407
409
|
|
|
408
410
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.8",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"!**/*.test.mjs",
|
|
37
37
|
"!**/*_test.rego",
|
|
38
38
|
"!**/test-helpers.mjs",
|
|
39
|
-
"!**/fixtures/**"
|
|
39
|
+
"!**/fixtures/**",
|
|
40
|
+
"!**/__fixtures__/**"
|
|
40
41
|
],
|
|
41
42
|
"type": "module",
|
|
42
43
|
"types": "./types/bin/n-cursor.d.ts",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"oxc-parser": "^0.128.0",
|
|
49
50
|
"picomatch": "^4.0.4",
|
|
51
|
+
"smol-toml": "^1.6.1",
|
|
50
52
|
"yaml": "^2.8.3"
|
|
51
53
|
},
|
|
52
54
|
"engines": {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє GitHub Actions за правилом ga.mdc.
|
|
3
3
|
*
|
|
4
|
-
* Workflows лише з розширенням `.yml`, наявність clean/lint workflow,
|
|
5
|
-
* відсутність MegaLinter,
|
|
4
|
+
* Workflows лише з розширенням `.yml`, наявність clean/lint workflow,
|
|
5
|
+
* відсутність MegaLinter, виклик у `lint-ga.yml`,
|
|
6
6
|
* наявність composite `.github/actions/setup-bun-deps/action.yml` (його записує npx `\@nitra/cursor`),
|
|
7
|
-
* `\.vscode/settings.json` — `editor.defaultFormatter` **oxc** для `[github-actions-workflow]`.
|
|
8
7
|
*
|
|
9
8
|
* Структурні поля 4 канонічних workflow (`clean-ga-workflows.yml`, `clean-merged-branch.yml`,
|
|
10
9
|
* `lint-ga.yml`, `git-ai.yml`) і УНІВЕРСАЛЬНІ перевірки для всіх `.github/workflows/*.yml`
|
|
11
10
|
* (`concurrency`, заборонені `oven-sh/setup-bun` / `actions/cache` / `bun install` у `uses`/`run`,
|
|
12
11
|
* shell-продовження `\` у `run`, обов'язковий `actions/checkout@v6` перед локальним
|
|
13
|
-
* `setup-bun-deps`)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `setup-bun-deps`), а також `package.json`, `.vscode/*` і `.github/zizmor.yml` —
|
|
13
|
+
* у Rego-полісі під `npm/policy/ga/`. Тут лишилися FS/git/tooling перевірки:
|
|
14
|
+
* наявність файлів, MegaLinter leftovers, `on.*.paths` через `git ls-files :(glob)`,
|
|
15
|
+
* і локальний `shellcheck`.
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync } from 'node:fs'
|
|
18
18
|
import { readdir, readFile } from 'node:fs/promises'
|
|
@@ -30,8 +30,6 @@ const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/
|
|
|
30
30
|
/** Типові конфіги MegaLinter у корені репо */
|
|
31
31
|
const MEGALINTER_CONFIG_NAMES = ['.mega-linter.yml', '.megalinter.yaml', '.mega-linter.yaml']
|
|
32
32
|
|
|
33
|
-
const N_CURSOR_LINT_GA_RE = /\bn-cursor\s+lint-ga\b/
|
|
34
|
-
|
|
35
33
|
/** Обовʼязкові workflow-файли (ga.mdc). */
|
|
36
34
|
const REQUIRED_WORKFLOWS = ['clean-ga-workflows.yml', 'clean-merged-branch.yml', 'lint-ga.yml', 'git-ai.yml']
|
|
37
35
|
|
|
@@ -179,92 +177,6 @@ async function checkMegalinter(wfDir, ymlWorkflows, passFn, failFn) {
|
|
|
179
177
|
if (!found) passFn('Залишків MegaLinter не виявлено')
|
|
180
178
|
}
|
|
181
179
|
|
|
182
|
-
/**
|
|
183
|
-
* Перевіряє zizmor конфіг.
|
|
184
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
185
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
186
|
-
*/
|
|
187
|
-
async function checkZizmor(passFn, failFn) {
|
|
188
|
-
const zizmorPath = '.github/zizmor.yml'
|
|
189
|
-
if (!existsSync(zizmorPath)) {
|
|
190
|
-
failFn(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
const z = await readFile(zizmorPath, 'utf8')
|
|
194
|
-
passFn(`${zizmorPath} існує`)
|
|
195
|
-
if (z.includes('ref-pin')) {
|
|
196
|
-
passFn(`${zizmorPath} містить політику ref-pin (zizmor)`)
|
|
197
|
-
} else {
|
|
198
|
-
failFn(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Перевіряє `.vscode/settings.json`: oxfmt/oxc як default formatter для GitHub Actions workflow (мова
|
|
204
|
-
* `github-actions-workflow` з розширення github.vscode-github-actions), узгоджено з oxc для yaml/workflow.
|
|
205
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
206
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
207
|
-
*/
|
|
208
|
-
async function checkVscodeSettingsForGa(passFn, failFn) {
|
|
209
|
-
const rel = '.vscode/settings.json'
|
|
210
|
-
if (!existsSync(rel)) {
|
|
211
|
-
failFn(`${rel} не існує — додай [github-actions-workflow].editor.defaultFormatter = oxc.oxc-vscode (ga.mdc)`)
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
let settings
|
|
215
|
-
try {
|
|
216
|
-
settings = JSON.parse(await readFile(rel, 'utf8'))
|
|
217
|
-
} catch {
|
|
218
|
-
failFn(`${rel}: невалідний JSON (ga.mdc)`)
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
if (!settings || typeof settings !== 'object') {
|
|
222
|
-
failFn(`${rel}: очікується об’єкт налаштувань (ga.mdc)`)
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
const block = /** @type {Record<string, unknown>} */ (settings)['[github-actions-workflow]']
|
|
226
|
-
if (!block || typeof block !== 'object' || block === null || Array.isArray(block)) {
|
|
227
|
-
failFn(`${rel}: додай "[github-actions-workflow]": { "editor.defaultFormatter": "oxc.oxc-vscode" } (ga.mdc)`)
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
const df = String(/** @type {Record<string, unknown>} */ (block)['editor.defaultFormatter'] ?? '')
|
|
231
|
-
if (df !== 'oxc.oxc-vscode') {
|
|
232
|
-
failFn(
|
|
233
|
-
`${rel}: [github-actions-workflow].editor.defaultFormatter має бути "oxc.oxc-vscode" (зараз: ${df || '∅'}) (ga.mdc)`
|
|
234
|
-
)
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
passFn(`${rel}: [github-actions-workflow] → oxc.oxc-vscode`)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Перевіряє скрипт lint-ga в package.json.
|
|
242
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
243
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
244
|
-
*/
|
|
245
|
-
async function checkLintGaScript(passFn, failFn) {
|
|
246
|
-
if (!existsSync('package.json')) {
|
|
247
|
-
failFn('package.json не існує — потрібен lint-ga у scripts')
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
251
|
-
const lg = pkg.scripts?.['lint-ga']
|
|
252
|
-
if (typeof lg !== 'string') {
|
|
253
|
-
failFn('package.json: додай скрипт "lint-ga" (ga.mdc)')
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
passFn('package.json містить lint-ga')
|
|
257
|
-
// Канонічний скрипт делегує виконання CLI `n-cursor lint-ga` (bin з `@nitra/cursor`) — там preflight
|
|
258
|
-
// на shellcheck + послідовно `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
|
|
259
|
-
// Виклик через bin-ім’я `n-cursor`, а не `npx --no @nitra/cursor`, бо `bun run` транслює `npx` у `bun x`,
|
|
260
|
-
// а `bun x @nitra/cursor` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання.
|
|
261
|
-
if (N_CURSOR_LINT_GA_RE.test(lg)) {
|
|
262
|
-
passFn('lint-ga делегує CLI n-cursor lint-ga (preflight shellcheck + actionlint + zizmor)')
|
|
263
|
-
} else {
|
|
264
|
-
failFn('lint-ga має бути "n-cursor lint-ga" — CLI робить preflight shellcheck перед actionlint/zizmor (ga.mdc)')
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
180
|
/**
|
|
269
181
|
* Перевіряє наявність локального `shellcheck` у PATH. `actionlint` (`bunx github-actionlint`)
|
|
270
182
|
* запускає shell-перевірки в кроках `run:` workflow тільки коли `shellcheck` доступний; інакше
|
|
@@ -437,19 +349,6 @@ export async function check() {
|
|
|
437
349
|
await checkApplyWorkflow(wfDir, files, 'apply-k8s.yml', '**/k8s/**/*.yaml', pass, fail)
|
|
438
350
|
await checkApplyWorkflow(wfDir, files, 'apply-nats-consumer.yml', '**/consumer.yaml', pass, fail)
|
|
439
351
|
|
|
440
|
-
if (existsSync('.vscode/extensions.json')) {
|
|
441
|
-
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
442
|
-
if (ext.recommendations?.includes('github.vscode-github-actions')) {
|
|
443
|
-
pass('extensions.json містить github.vscode-github-actions')
|
|
444
|
-
} else {
|
|
445
|
-
fail('extensions.json не містить github.vscode-github-actions')
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
fail('.vscode/extensions.json не існує')
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
await checkVscodeSettingsForGa(pass, fail)
|
|
452
|
-
|
|
453
352
|
await checkMegalinter(wfDir, ymlWorkflows, pass, fail)
|
|
454
353
|
|
|
455
354
|
// git-залежна перевірка `on.push.paths` glob-ів (вимагає `git ls-files`) —
|
|
@@ -462,8 +361,6 @@ export async function check() {
|
|
|
462
361
|
}
|
|
463
362
|
}
|
|
464
363
|
|
|
465
|
-
await checkZizmor(pass, fail)
|
|
466
|
-
await checkLintGaScript(pass, fail)
|
|
467
364
|
checkShellcheckInstalled(pass, fail)
|
|
468
365
|
|
|
469
366
|
return reporter.getExitCode()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Перевірка кореневого `package.json` для GitHub Actions tooling (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Структурні workflow-перевірки живуть у `ga.workflow_common` і per-workflow
|
|
4
|
+
# policy-пакетах. JS лишається для PATH-preflight (`shellcheck`) і git-залежної
|
|
5
|
+
# перевірки `on.*.paths` через `git ls-files`.
|
|
6
|
+
#
|
|
7
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
8
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
9
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
10
|
+
package ga.package_json
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
not is_string(object.get(object.get(input, "scripts", {}), "lint-ga", null))
|
|
16
|
+
msg := "package.json: додай скрипт \"lint-ga\" (ga.mdc)"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deny contains msg if {
|
|
20
|
+
lint_ga := object.get(object.get(input, "scripts", {}), "lint-ga", "")
|
|
21
|
+
is_string(lint_ga)
|
|
22
|
+
not regex.match(`\bn-cursor\s+lint-ga\b`, lint_ga)
|
|
23
|
+
msg := "lint-ga має делегувати CLI `n-cursor lint-ga` (ga.mdc)"
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для GitHub Actions (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Canonical: у `recommendations` має бути `github.vscode-github-actions`.
|
|
4
|
+
# Додаткові рекомендації від інших правил дозволені.
|
|
5
|
+
#
|
|
6
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
7
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
8
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
9
|
+
package ga.vscode_extensions
|
|
10
|
+
|
|
11
|
+
import rego.v1
|
|
12
|
+
|
|
13
|
+
deny contains msg if {
|
|
14
|
+
not "github.vscode-github-actions" in {r | some r in object.get(input, "recommendations", [])}
|
|
15
|
+
msg := ".vscode/extensions.json: recommendations має містити \"github.vscode-github-actions\" (ga.mdc)"
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
|
|
3
|
+
"files": {
|
|
4
|
+
"single": ".vscode/settings.json",
|
|
5
|
+
"required": true
|
|
6
|
+
},
|
|
7
|
+
"missingMessage": ".vscode/settings.json не існує — додай [github-actions-workflow].editor.defaultFormatter (ga.mdc)"
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Перевірка `.vscode/settings.json` для GitHub Actions workflow (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Мова `github-actions-workflow` має форматуватись через `oxc.oxc-vscode`,
|
|
4
|
+
# узгоджено з oxc для YAML/workflow.
|
|
5
|
+
#
|
|
6
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
7
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
8
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
9
|
+
package ga.vscode_settings
|
|
10
|
+
|
|
11
|
+
import rego.v1
|
|
12
|
+
|
|
13
|
+
deny contains msg if {
|
|
14
|
+
block := object.get(input, "[github-actions-workflow]", null)
|
|
15
|
+
not is_object(block)
|
|
16
|
+
msg := ".vscode/settings.json: додай \"[github-actions-workflow]\": { \"editor.defaultFormatter\": \"oxc.oxc-vscode\" } (ga.mdc)"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deny contains msg if {
|
|
20
|
+
block := object.get(input, "[github-actions-workflow]", null)
|
|
21
|
+
is_object(block)
|
|
22
|
+
object.get(block, "editor.defaultFormatter", null) != "oxc.oxc-vscode"
|
|
23
|
+
msg := ".vscode/settings.json: [github-actions-workflow].editor.defaultFormatter має бути \"oxc.oxc-vscode\" (ga.mdc)"
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Перевірка `.github/zizmor.yml` для GitHub Actions (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# JS раніше перевіряв сирий текст на `ref-pin`; у policy це робиться по
|
|
4
|
+
# JSON-представленню розпарсеного YAML-документа. Коментарі не враховуються,
|
|
5
|
+
# тож збіг має бути у фактичній конфігурації.
|
|
6
|
+
#
|
|
7
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
8
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
9
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
10
|
+
package ga.zizmor_yml
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
not contains(json.marshal(input), "ref-pin")
|
|
16
|
+
msg := ".github/zizmor.yml: додай policies ref-pin для unpinned-uses (ga.mdc)"
|
|
17
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє лінт JavaScript за правилом js-lint.mdc.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Flat ESLint з getConfig і ignore для auto-imports,
|
|
5
5
|
* `.oxlintrc.json` має збігатися з каноном oxlint у пакеті (`npm/scripts/utils/oxlint-canonical.json`):
|
|
6
6
|
* plugins, jsPlugins, categories, усі правила з канону (додаткові записи в `rules` дозволені), settings, env,
|
|
7
|
-
* globals, ignorePatterns.
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
* `@
|
|
11
|
-
* `lint-js.yml`
|
|
12
|
-
* `engines.bun` >= 1.3, `"type": "module"` у кореневому і всіх workspace `package.json`. Дубль перевірки JS у `lint.yml` — заборонено.
|
|
7
|
+
* globals, ignorePatterns. Також перевіряє workspace `package.json` на `type: "module"`
|
|
8
|
+
* і `engines`, workflow-дубль у `lint.yml`, `knip.json` autofill і застарілі `.eslintrc*`.
|
|
9
|
+
*
|
|
10
|
+
* Per-document вимоги (`lint-js`, `@nitra/eslint-config`, root `engines`, `.jscpd.json`,
|
|
11
|
+
* `.vscode/extensions.json`, `lint-js.yml`) — у policy-пакетах `js_lint.*`.
|
|
13
12
|
*/
|
|
14
13
|
import { existsSync } from 'node:fs'
|
|
15
14
|
import { copyFile, readFile } from 'node:fs/promises'
|
|
@@ -42,9 +41,6 @@ export const KNIP_CANONICAL_JSON_PATH = join(
|
|
|
42
41
|
'knip-canonical.json'
|
|
43
42
|
)
|
|
44
43
|
|
|
45
|
-
/** Мінімальні рекомендації розширень редактора з js-lint.mdc (eslint, oxlint, GA). */
|
|
46
|
-
export const REQUIRED_VSCODE_EXTENSIONS = ['dbaeumer.vscode-eslint', 'github.vscode-github-actions', 'oxc.oxc-vscode']
|
|
47
|
-
|
|
48
44
|
const NON_DIGITS_RE = /\D+/u
|
|
49
45
|
|
|
50
46
|
// Канонічний рядок `lint-js`-скрипта і мінімальна версія `@nitra/eslint-config` —
|
|
@@ -353,36 +349,6 @@ async function checkOxlintRc(passFn, failFn) {
|
|
|
353
349
|
}
|
|
354
350
|
}
|
|
355
351
|
|
|
356
|
-
/**
|
|
357
|
-
* Перевіряє .vscode/extensions.json на потрібні розширення.
|
|
358
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
359
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
360
|
-
*/
|
|
361
|
-
async function checkVscodeExtensions(passFn, failFn) {
|
|
362
|
-
if (!existsSync('.vscode/extensions.json')) {
|
|
363
|
-
failFn('.vscode/extensions.json не існує — додай recommendations з js-lint.mdc (див. check-js-lint.mjs)')
|
|
364
|
-
return
|
|
365
|
-
}
|
|
366
|
-
let ext
|
|
367
|
-
try {
|
|
368
|
-
ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
369
|
-
} catch {
|
|
370
|
-
failFn('.vscode/extensions.json не є валідним JSON')
|
|
371
|
-
return
|
|
372
|
-
}
|
|
373
|
-
const rec = ext.recommendations
|
|
374
|
-
if (!Array.isArray(rec)) {
|
|
375
|
-
failFn('.vscode/extensions.json: поле recommendations має бути масивом')
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
const missing = REQUIRED_VSCODE_EXTENSIONS.filter(id => !rec.includes(id))
|
|
379
|
-
if (missing.length > 0) {
|
|
380
|
-
failFn(`.vscode/extensions.json: додай у recommendations: ${missing.join(', ')} (мінімум для js-lint.mdc)`)
|
|
381
|
-
} else {
|
|
382
|
-
passFn('.vscode/extensions.json: є рекомендації oxlint, eslint і GitHub Actions')
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
352
|
/**
|
|
387
353
|
* FS-existence для `lint-js.yml` + cross-file перевірка, що `lint.yml` (якщо існує)
|
|
388
354
|
* не дублює лінт JS-кроки. Структуру `lint-js.yml` (`actions/checkout@v6`,
|
|
@@ -408,47 +374,6 @@ async function checkLintJsWorkflows(passFn, failFn) {
|
|
|
408
374
|
}
|
|
409
375
|
}
|
|
410
376
|
|
|
411
|
-
/**
|
|
412
|
-
* Перевіряє .jscpd.json.
|
|
413
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
414
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
415
|
-
*/
|
|
416
|
-
async function checkJscpdConfig(passFn, failFn) {
|
|
417
|
-
if (!existsSync('.jscpd.json')) {
|
|
418
|
-
failFn('.jscpd.json не існує — створи з полями згідно check js-lint')
|
|
419
|
-
return
|
|
420
|
-
}
|
|
421
|
-
let jscpdCfg
|
|
422
|
-
try {
|
|
423
|
-
jscpdCfg = JSON.parse(await readFile('.jscpd.json', 'utf8'))
|
|
424
|
-
} catch {
|
|
425
|
-
failFn('.jscpd.json не є валідним JSON')
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
passFn('.jscpd.json існує')
|
|
429
|
-
if (jscpdCfg.gitignore === true) {
|
|
430
|
-
passFn('.jscpd.json: gitignore увімкнено')
|
|
431
|
-
} else {
|
|
432
|
-
failFn('.jscpd.json має містити "gitignore": true')
|
|
433
|
-
}
|
|
434
|
-
if (jscpdCfg.exitCode === 1) {
|
|
435
|
-
passFn('.jscpd.json: exitCode 1 при дублікатах')
|
|
436
|
-
} else {
|
|
437
|
-
failFn('.jscpd.json має містити "exitCode": 1 (інакше CI не впаде на клонах)')
|
|
438
|
-
}
|
|
439
|
-
if (Array.isArray(jscpdCfg.reporters) && jscpdCfg.reporters.includes('console')) {
|
|
440
|
-
passFn('.jscpd.json: reporters містить console')
|
|
441
|
-
} else {
|
|
442
|
-
failFn('.jscpd.json має містити "reporters": ["console"] (або масив із "console")')
|
|
443
|
-
}
|
|
444
|
-
const minLines = jscpdCfg.minLines
|
|
445
|
-
if (typeof minLines === 'number' && minLines >= 25) {
|
|
446
|
-
passFn(`.jscpd.json: minLines ${minLines} (>=25)`)
|
|
447
|
-
} else {
|
|
448
|
-
failFn('.jscpd.json має містити "minLines" як число >= 25')
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
377
|
/**
|
|
453
378
|
* Перевіряє наявність `knip.json` у корені проєкту. Якщо файл відсутній —
|
|
454
379
|
* копіює канонічний `knip-canonical.json` з пакета `@nitra/cursor` як стартовий
|
|
@@ -485,9 +410,7 @@ export async function check() {
|
|
|
485
410
|
await checkEslintConfig(pass, fail)
|
|
486
411
|
await checkPackageJsonJsLint(pass, fail)
|
|
487
412
|
await checkOxlintRc(pass, fail)
|
|
488
|
-
await checkVscodeExtensions(pass, fail)
|
|
489
413
|
await checkLintJsWorkflows(pass, fail)
|
|
490
|
-
await checkJscpdConfig(pass, fail)
|
|
491
414
|
await checkKnipConfig(pass, fail)
|
|
492
415
|
|
|
493
416
|
for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Перевірка `.jscpd.json` для js-lint (js-lint.mdc).
|
|
2
|
+
#
|
|
3
|
+
# JS-частина лишається для FS/cross-file: наявність workflow, flat ESLint config,
|
|
4
|
+
# `.oxlintrc.json` проти embedded canonical snapshot, `knip.json` autofill.
|
|
5
|
+
# Цей пакет покриває лише структуру одного JSON-документа.
|
|
6
|
+
#
|
|
7
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
8
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
9
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
10
|
+
package js_lint.jscpd
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
input.gitignore != true
|
|
16
|
+
msg := ".jscpd.json має містити \"gitignore\": true (js-lint.mdc)"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deny contains msg if {
|
|
20
|
+
input.exitCode != 1
|
|
21
|
+
msg := ".jscpd.json має містити \"exitCode\": 1 (інакше CI не впаде на клонах) (js-lint.mdc)"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
deny contains msg if {
|
|
25
|
+
not "console" in {r | some r in object.get(input, "reporters", [])}
|
|
26
|
+
msg := ".jscpd.json має містити \"reporters\": [\"console\"] або масив із \"console\" (js-lint.mdc)"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
not is_number(object.get(input, "minLines", null))
|
|
31
|
+
msg := ".jscpd.json має містити \"minLines\" як число >= 25 (js-lint.mdc)"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
deny contains msg if {
|
|
35
|
+
is_number(input.minLines)
|
|
36
|
+
input.minLines < 25
|
|
37
|
+
msg := ".jscpd.json має містити \"minLines\" як число >= 25 (js-lint.mdc)"
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для js-lint (js-lint.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Canonical: у `recommendations` мають бути ESLint, GitHub Actions і Oxlint.
|
|
4
|
+
# Канон задає мінімум — додаткові рекомендації від інших правил дозволені.
|
|
5
|
+
#
|
|
6
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
7
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
8
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
9
|
+
package js_lint.vscode_extensions
|
|
10
|
+
|
|
11
|
+
import rego.v1
|
|
12
|
+
|
|
13
|
+
required_extensions := {
|
|
14
|
+
"dbaeumer.vscode-eslint",
|
|
15
|
+
"github.vscode-github-actions",
|
|
16
|
+
"oxc.oxc-vscode",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
recommendations_set := {r | some r in object.get(input, "recommendations", [])}
|
|
20
|
+
|
|
21
|
+
deny contains msg if {
|
|
22
|
+
some required in required_extensions
|
|
23
|
+
not required in recommendations_set
|
|
24
|
+
msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js-lint.mdc)", [required])
|
|
25
|
+
}
|