@nitra/cursor 1.13.2 → 1.13.11
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 +102 -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/ga.mdc +10 -15
- package/rules/ga/policy/package_json/package_json.rego +20 -0
- package/rules/ga/policy/package_json/target.json +8 -0
- package/rules/ga/policy/package_json/template/package.json.contains.json +1 -0
- package/rules/ga/policy/vscode_extensions/target.json +8 -0
- package/rules/ga/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +18 -0
- package/rules/ga/policy/vscode_settings/target.json +8 -0
- package/rules/ga/policy/vscode_settings/template/settings.json.snippet.json +1 -0
- package/rules/ga/policy/vscode_settings/vscode_settings.rego +22 -0
- package/rules/ga/policy/zizmor_yml/target.json +8 -0
- package/rules/ga/policy/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
- package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +27 -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/rego/lint/lint.mjs +5 -4
- package/rules/rego/policy/package_json/package_json.rego +8 -29
- package/rules/rego/policy/package_json/template/package.json.snippet.json +1 -0
- package/rules/rego/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +7 -11
- package/rules/rego/policy/vscode_settings/template/settings.json.snippet.json +6 -0
- package/rules/rego/policy/vscode_settings/vscode_settings.rego +19 -27
- package/rules/rego/rego.mdc +10 -8
- 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/security/todo.MD +27 -0
- 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,104 @@
|
|
|
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.11] - 2026-05-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `rego` rule template/ міграція (Phase 3): 3 концерни — `package_json` (snippet із збереженням `trim_space` tolerance), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object 2-level + окремий deny на non-object block).
|
|
12
|
+
- Drift-тести у кожному `*_test.rego`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `rego.package_json.rego` — замість двох inline-deny (missing + wrong-value через `regex/trim_space`) тепер один snippet-walker через `data.template.snippet`.
|
|
17
|
+
- `rego.vscode_extensions.rego` — замість inline `"tsandall.opa"` тепер subset-of через `data.template.snippet.recommendations`.
|
|
18
|
+
- `rego.vscode_settings.rego` — 2-рівневий snippet-walker з гардом `is_object(inner)` для випадку, коли block існує, але не обʼєкт.
|
|
19
|
+
- `rego.mdc` — inline `package.json` snippet замінено на template-link; додано посилання на `.vscode/{extensions,settings}.json` template-файли. Виправлено застаріле `Цілі — npm/policy/` → `npm/rules/`.
|
|
20
|
+
- `docs/adr/template-dir-concern-inventory.md` — позначено 3 `rego.*` концерни як ✓; додано Phase 3 у прогрес-секцію.
|
|
21
|
+
|
|
22
|
+
## [1.13.10] - 2026-05-17
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `runLintRego` (`npm/rules/rego/lint/lint.mjs`) — `LINT_TARGETS` вказував на застарілий шлях `npm/policy` (не існує після Phase 1 реструктуризації), тож `bun run lint-rego` мовчки exit 0 без реальної перевірки. Тепер `LINT_TARGETS = ['npm/rules']` — `opa check --strict`, `regal lint`, `conftest verify` реально проходять по всіх 111 `.rego`-файлах. TDD-регресія у `lint.test.mjs` (broken-syntax + well-formed fixtures).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `.regal/config.yaml` — додано `idiomatic.directory-package-mismatch` і `imports.unresolved-reference` у `ignore` (інтенціональні конвенції проєкту: package = `<rule>.<concern>` у `<rule>/policy/<concern>/`; `data.template.*` ін'єктиться runtime через `--data`). `style.line-length.max-line-length: 220` — узгоджено з `opa fmt` (тримає малі обʼєкти single-line).
|
|
31
|
+
- `*_test.rego` з порушенням `test-outside-test-package` (4 файли: `js-lint.jscpd`, `js-lint.vscode_extensions`, `security.gitleaks`, `vue.package_json`) — перейменовано в `<package>_test` із явним `import data.<package>`.
|
|
32
|
+
- `opa fmt -w npm/rules` — auto-fix форматування.
|
|
33
|
+
- `docs/adr/template-dir-concern-inventory.md` — додано 4 `ga.*` концерни з відміткою `✓` (мігровано); оновлено summary-числа (85 концернів, 39 з template — 46%); додано секцію прогресу міграції.
|
|
34
|
+
|
|
35
|
+
## [1.13.9] - 2026-05-17
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- `ga` rule template/ міграція (Phase 2): 4 концерни — `package_json` (contains-style), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object), `zizmor_yml` (snippet з канонічним path `rules.unpinned-uses.config.policies."*"`).
|
|
40
|
+
- Drift-тести (`test_data_template_drives_*`) у кожному `*_test.rego` ловлять регресію, якщо rego перестане читати з `data.template`.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- `ga.package_json.rego` — замість двох inline-deny з `is_string` + `regex.match` тепер один generic contains-walker через `data.template.contains`.
|
|
45
|
+
- `ga.vscode_extensions.rego` — замість inline `"github.vscode-github-actions"` тепер subset-of через `data.template.snippet.recommendations`.
|
|
46
|
+
- `ga.vscode_settings.rego` — 2-рівневий snippet-walker через `data.template.snippet` (літеральні keys `[github-actions-workflow]`, `editor.defaultFormatter`).
|
|
47
|
+
- `ga.zizmor_yml.rego` — замість substring `json.marshal` хака тепер структурний чек `rules.unpinned-uses.config.policies."*"` із expected value з `data.template.snippet`.
|
|
48
|
+
- `ga.mdc` — inline `package.json` snippet і `zizmor.yml` snippet блоки замінено на markdown-посилання на template-файли; додано посилання на нові template/ для `.vscode/{extensions,settings}.json`.
|
|
49
|
+
|
|
50
|
+
## [1.13.8] - 2026-05-17
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- Перенесено частину per-document логіки з `fix` у Rego policy:
|
|
55
|
+
- `js-lint`: `.jscpd.json` і `.vscode/extensions.json`;
|
|
56
|
+
- `ga`: `package.json#scripts.lint-ga`, `.vscode/extensions.json`, `.vscode/settings.json`, `.github/zizmor.yml`;
|
|
57
|
+
- `security`: `.gitleaks.toml` (`[extend].useDefault = true`);
|
|
58
|
+
- `vue`: залежності Vue/Vite-пакетів і заборону `esbuild`.
|
|
59
|
+
- Відповідні JS check-и спрощено до FS/cross-file/AST/tooling частини без дублювання Rego-умов.
|
|
60
|
+
- `ensureNitraCursorInRootDevDependencies` тепер додає `@nitra/cursor` тільки в `package.json` поруч із запуском, якщо в ньому є `workspaces`.
|
|
61
|
+
- `vue.mdc` уточнює тестування через Bun Test Runner + Vue Test Utils/happy-dom замість Vitest/jsdom.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- `npm/package.json#devDependencies` — прибрано self-reference `@nitra/cursor`, щоб published package знову відповідав `npm-module` compact-package canon.
|
|
66
|
+
|
|
67
|
+
## [1.13.7] - 2026-05-17
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- `inlineTemplateLinks`: `String.replace(needle, replacement)` інтерпретує `$'`, `$&` тощо у `replacement`. Через це інлайнінг `.gitleaks.toml.snippet.toml` (де є `$'''`) ламав вивід — хвіст `.mdc` реінжектився всередину блока. Перехід на function-replacer (`(_) => replacement`) усуває це. Додано регресійний тест із фікстурою `with-dollar.toml`.
|
|
72
|
+
|
|
73
|
+
## [1.13.6] - 2026-05-17
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- `npm/scripts/utils/inline-template-links.mjs` — `inlineTemplateLinks(text, ruleDir)`: під час sync знаходить markdown-лінки виду `[label](./…/template/…)` у `.mdc` і замінює їх inline fenced-блоком з вмістом відповідного файла. Відсутній файл — hard error (fail loud).
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- `readBundledRuleContent` у `npm/bin/n-cursor.js` тепер пропускає текст правила через `inlineTemplateLinks` перед записом у `.cursor/rules/n-*.mdc`. Template-посилання у скопійованих правилах більше не зламані.
|
|
82
|
+
|
|
83
|
+
## [1.13.5] - 2026-05-17
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- Оркестратор `run-rule.mjs` тепер викликає `findMissingMdcRefs` для кожного правила — fail, якщо файл у `template/` не згаданий як markdown-посилання у `<id>.mdc`. Поки що активно лише для `security` (єдине правило з `template/`); готова страховка для Phase 2+.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- `check-mdc-template-refs.test.mjs` тест 3 — фіксував дубль test 1; тепер використовує окрему `no-templates` фікстуру, що дійсно валідує "no template/ dirs → empty result".
|
|
92
|
+
|
|
93
|
+
## [1.13.4] - 2026-05-17
|
|
94
|
+
|
|
95
|
+
### Removed
|
|
96
|
+
|
|
97
|
+
- `npm/package.json#devDependencies` — повторно видалено self-reference `@nitra/cursor` (порушує canon `npm-module`: «devDependencies не публікуються користувачам пакета»). Автоматично повертався у попередніх тасках template-dir роботи; цей коміт остаточно прибирає.
|
|
98
|
+
|
|
99
|
+
## [1.13.3] - 2026-05-17
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- `security/security.mdc` — прибрано inline merge-фрагменти (package.json snippet для `lint-security`, .gitleaks.toml повний канон), замість них markdown-посилання на файли в `template/` (single source of truth). Зміст правила залишається (описи для чого потрібен gitleaks, GitHub Actions), видалено дублювання фіксованого коду.
|
|
104
|
+
|
|
7
105
|
## [1.13.2] - 2026-05-17
|
|
8
106
|
|
|
9
107
|
### Changed
|
|
@@ -20,6 +118,10 @@
|
|
|
20
118
|
|
|
21
119
|
### Changed
|
|
22
120
|
|
|
121
|
+
- `security/fix/gitleaks/check.mjs` читає канон з `template/`, не з inline regex.
|
|
122
|
+
- `security/policy/package_json/package_json.rego` читає очікувані значення з `data.template.*`, не з inline literals.
|
|
123
|
+
- Оркестратор `run-rule.mjs` для policy-концернів вантажить `template/` через `resolveConcernTemplateData` і передає у `runConftestBatch.templateData`.
|
|
124
|
+
- Снепет `.gitleaks.toml.snippet.toml` тримає канонічний title + allowlist paths (description лишається user-specific).
|
|
23
125
|
- **9 правил переведено з `alwaysApply: true` на `alwaysApply: false` + `globs:`** — AI-контекст у Cursor/Claude Code підвантажується лише при роботі з релевантними файлами; програмна валідація через `npx check <rule>` залишається повністю функціональною незалежно від AI-контексту. Економить контекстне вікно у сесіях, де редагують код, далекий від відповідних конфігів.
|
|
24
126
|
- **`bun`** (`1.7 → 1.8`) — `globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"`
|
|
25
127
|
- **`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.11",
|
|
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()
|
package/rules/ga/ga.mdc
CHANGED
|
@@ -242,11 +242,7 @@ jobs:
|
|
|
242
242
|
|
|
243
243
|
**Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Канонічний скрипт у корені делегує виконання CLI `n-cursor lint-ga` (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`), який робить preflight на `shellcheck` і послідовно запускає `actionlint` та `zizmor`:
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
"scripts": {
|
|
247
|
-
"lint-ga": "n-cursor lint-ga"
|
|
248
|
-
}
|
|
249
|
-
```
|
|
245
|
+
- `package.json` — `scripts.lint-ga` має містити `n-cursor lint-ga`: [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
|
|
250
246
|
|
|
251
247
|
> Не використовуй `npx --no @nitra/cursor lint-ga` — `bun run` автоматично транслює `npx` у `bun x`, а `bun x` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання. Виклик через bin-ім’я `n-cursor` працює і у `bun run`, і у `npm run`.
|
|
252
248
|
|
|
@@ -254,16 +250,15 @@ CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, поті
|
|
|
254
250
|
|
|
255
251
|
**`.github/zizmor.yml`:** для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection):
|
|
256
252
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```
|
|
253
|
+
- Канон `.github/zizmor.yml`: [zizmor.yml.snippet.yml](./policy/zizmor_yml/template/zizmor.yml.snippet.yml)
|
|
254
|
+
|
|
255
|
+
**`.vscode/extensions.json`** має рекомендувати `github.vscode-github-actions`:
|
|
256
|
+
|
|
257
|
+
- Канон: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
|
|
258
|
+
|
|
259
|
+
**`.vscode/settings.json`** для мови `github-actions-workflow` має `editor.defaultFormatter = "oxc.oxc-vscode"`:
|
|
260
|
+
|
|
261
|
+
- Канон: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
|
|
267
262
|
|
|
268
263
|
**MegaLinter:** не використовувати; прибрати workflow, конфіги (`.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`), залежності та згадки в CI / pre-commit / документації.
|
|
269
264
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Перевірка кореневого `package.json` для GitHub Actions tooling (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.contains.json.
|
|
5
|
+
#
|
|
6
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
7
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
8
|
+
package ga.package_json
|
|
9
|
+
|
|
10
|
+
import rego.v1
|
|
11
|
+
|
|
12
|
+
# Кожне рядкове поле з contains має містити кожен substring.
|
|
13
|
+
# Відсутність ключа → `""` → contains() = false → deny.
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
some script_name, needles in data.template.contains.scripts
|
|
16
|
+
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
17
|
+
some needle in needles
|
|
18
|
+
not contains(actual, needle)
|
|
19
|
+
msg := sprintf("package.json: scripts.%s має містити %q (ga.mdc)", [script_name, needle])
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "scripts": { "lint-ga": ["n-cursor lint-ga"] } }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "recommendations": ["github.vscode-github-actions"] }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для GitHub Actions (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/extensions.json.snippet.json.
|
|
5
|
+
# `recommendations` — subset-of: кожна рекомендація з template має бути у input.
|
|
6
|
+
# Додаткові рекомендації від інших правил дозволені.
|
|
7
|
+
#
|
|
8
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
9
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
10
|
+
package ga.vscode_extensions
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
some rec in data.template.snippet.recommendations
|
|
16
|
+
not rec in {r | some r in object.get(input, "recommendations", [])}
|
|
17
|
+
msg := sprintf(".vscode/extensions.json: recommendations має містити %q (ga.mdc)", [rec])
|
|
18
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
{ "[github-actions-workflow]": { "editor.defaultFormatter": "oxc.oxc-vscode" } }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Перевірка `.vscode/settings.json` для GitHub Actions workflow (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/settings.json.snippet.json.
|
|
5
|
+
# Snippet — 2-рівнева мапа: <language-block-key>.<setting-key> = <expected>
|
|
6
|
+
# (VS Code-конвенція: ключі типу `[github-actions-workflow]` і `editor.defaultFormatter`
|
|
7
|
+
# — це літеральні string-keys із дужками/крапкою, не вкладені обʼєкти).
|
|
8
|
+
#
|
|
9
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
10
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
11
|
+
package ga.vscode_settings
|
|
12
|
+
|
|
13
|
+
import rego.v1
|
|
14
|
+
|
|
15
|
+
deny contains msg if {
|
|
16
|
+
some block_key, expected_inner in data.template.snippet
|
|
17
|
+
inner := object.get(input, block_key, {})
|
|
18
|
+
some leaf_key, expected_value in expected_inner
|
|
19
|
+
actual := object.get(inner, leaf_key, null)
|
|
20
|
+
actual != expected_value
|
|
21
|
+
msg := sprintf(".vscode/settings.json: %s.%s має бути %q (ga.mdc)", [block_key, leaf_key, expected_value])
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Перевірка `.github/zizmor.yml` для GitHub Actions (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/zizmor.yml.snippet.yml.
|
|
5
|
+
# Канонічний шлях — `rules.unpinned-uses.config.policies."*"`; expected value
|
|
6
|
+
# (наприклад `"ref-pin"`) приходить із template, path лишається тут.
|
|
7
|
+
#
|
|
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
|
+
expected := data.template.snippet.rules["unpinned-uses"].config.policies["*"]
|
|
16
|
+
policies := object.get(
|
|
17
|
+
object.get(
|
|
18
|
+
object.get(object.get(input, "rules", {}), "unpinned-uses", {}),
|
|
19
|
+
"config",
|
|
20
|
+
{},
|
|
21
|
+
),
|
|
22
|
+
"policies",
|
|
23
|
+
{},
|
|
24
|
+
)
|
|
25
|
+
object.get(policies, "*", null) != expected
|
|
26
|
+
msg := sprintf(".github/zizmor.yml: rules.unpinned-uses.config.policies[%q] має бути %q (ga.mdc)", ["*", expected])
|
|
27
|
+
}
|
|
@@ -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']) {
|