@nitra/cursor 1.13.1 → 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.
Files changed (38) hide show
  1. package/.claude-template/hooks/capture-decisions.sh +31 -13
  2. package/.claude-template/hooks/normalize-decisions.sh +8 -3
  3. package/CHANGELOG.md +65 -0
  4. package/bin/n-cursor.js +4 -2
  5. package/package.json +4 -2
  6. package/rules/adr/adr.mdc +14 -4
  7. package/rules/ga/fix/workflows/check.mjs +6 -109
  8. package/rules/ga/policy/package_json/package_json.rego +24 -0
  9. package/rules/ga/policy/package_json/target.json +8 -0
  10. package/rules/ga/policy/vscode_extensions/target.json +8 -0
  11. package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +16 -0
  12. package/rules/ga/policy/vscode_settings/target.json +8 -0
  13. package/rules/ga/policy/vscode_settings/vscode_settings.rego +24 -0
  14. package/rules/ga/policy/zizmor_yml/target.json +8 -0
  15. package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +17 -0
  16. package/rules/js-lint/fix/tooling/check.mjs +6 -83
  17. package/rules/js-lint/policy/jscpd/jscpd.rego +38 -0
  18. package/rules/js-lint/policy/jscpd/target.json +8 -0
  19. package/rules/js-lint/policy/vscode_extensions/target.json +8 -0
  20. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +25 -0
  21. package/rules/security/fix/gitleaks/check.mjs +8 -45
  22. package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +12 -0
  23. package/rules/security/policy/gitleaks/gitleaks.rego +17 -0
  24. package/rules/security/policy/gitleaks/target.json +8 -0
  25. package/rules/security/policy/package_json/package_json.rego +22 -59
  26. package/rules/security/policy/package_json/template/package.json.contains.json +1 -0
  27. package/rules/security/policy/package_json/template/package.json.deny.json +4 -0
  28. package/rules/security/policy/package_json/template/package.json.snippet.json +1 -0
  29. package/rules/security/security.mdc +7 -26
  30. package/rules/vue/fix/packages/check.mjs +7 -64
  31. package/rules/vue/policy/package_json/package_json.rego +45 -2
  32. package/rules/vue/vue.mdc +15 -2
  33. package/scripts/ensure-nitra-cursor-dev-dependencies.mjs +41 -21
  34. package/scripts/utils/check-mdc-template-refs.mjs +47 -0
  35. package/scripts/utils/inline-template-links.mjs +60 -0
  36. package/scripts/utils/run-conftest-batch.mjs +60 -33
  37. package/scripts/utils/run-rule.mjs +16 -1
  38. package/scripts/utils/template.mjs +215 -0
@@ -83,33 +83,51 @@ if [[ -z "$TRANSCRIPT" ]]; then
83
83
  fi
84
84
 
85
85
  PROMPT=$(cat <<'EOF'
86
- You analyze a Claude Code session transcript and produce a durable knowledge artifact (ADR, Runbook, or Knowledge note) capturing what was done and why.
86
+ You analyze an AI coding session transcript and produce durable decision documentation.
87
87
 
88
- LANGUAGE: Write the ENTIRE output in Ukrainian. This applies to the title, all section content, prose, and rationale. Keep code identifiers, file paths, commands, and tool or library names in their original form (do not translate `walkDir`, `package.json`, `npm`, etc.) — only translate the natural-language prose around them. Section labels themselves stay in Ukrainian per the template below.
88
+ LANGUAGE: Write the content in Ukrainian. Keep MADR section headings in English exactly as shown below. Keep code identifiers, file paths, commands, and tool or library names in their original form (do not translate `walkDir`, `package.json`, `npm`, etc.).
89
89
 
90
90
  IMPORTANT: by "decision" we mean the design choice expressed in the session — even if the user pre-specified it in their brief. The user dictating the approach IS the decision; capture it, including the rationale they gave or that became apparent during implementation. Do NOT return NONE just because the user gave detailed instructions upfront.
91
91
 
92
+ ANTI-HALLUCINATION RULES:
93
+ - Use only facts present in the transcript, tool calls, changed file paths, or direct implications of those facts.
94
+ - Do not invent decision makers, stakeholders, business context, requirements, alternatives, or consequences.
95
+ - If alternatives were not discussed, write exactly: "Інші варіанти в transcript не обговорювалися."
96
+ - If a consequence is unknown, write it as "Neutral, because transcript не містить підтвердження наслідку."
97
+ - Prefer specific file paths and commands from the transcript over generic prose.
98
+
92
99
  OUTPUT RULES:
93
100
  - Emit one or more markdown blocks in this exact shape (no preamble, no trailing prose):
94
101
 
95
- ## [ADR|Runbook|Knowledge] <короткий заголовок українською>
96
- **Контекст:** <яка проблема / ситуація це спричинила — 1-2 речення>
97
- **Рішення/Процедура/Факт:** <що було зроблено — конкретно: змінені файли, введена семантика, кроки>
98
- **Обґрунтування:** <чому саме такий підхід з ТЗ користувача або міркувань асистента>
99
- **Розглянуті альтернативи:** <перелік явно обговорених, або «не обговорювалися»>
100
- **Зачіпає:** <файли, модулі, публічні API, конфіги>
102
+ ## ADR <короткий заголовок українською>
103
+
104
+ ## Context and Problem Statement
105
+ <1-3 речення: яка проблема / ситуація спричинила рішення.>
106
+
107
+ ## Considered Options
108
+ * <назва явно обговореного варіанта>
109
+ * <або "Інші варіанти в transcript не обговорювалися.">
110
+
111
+ ## Decision Outcome
112
+ Chosen option: "<назва обраного варіанта>", because <коротке обґрунтування з transcript>.
113
+
114
+ ### Consequences
115
+ * Good, because <підтверджений позитивний наслідок або "transcript фіксує очікувану користь: ...">.
116
+ * Bad, because <підтверджений негативний наслідок або "transcript не містить підтверджених негативних наслідків.">.
117
+
118
+ ## More Information
119
+ <файли, команди, публічні API, конфіги, transcript facts. Якщо нема — "Додаткової інформації в transcript не зафіксовано.">
101
120
 
102
121
  WHEN TO PICK EACH TYPE:
103
- - ADR: a design choice (library, schema, pattern, semantics of a field/API). Most substantive code work qualifies.
104
- - Runbook: a procedure to operate, fix, deploy, or reproduce something.
105
- - Knowledge: a non-obvious constraint, gotcha, or invariant uncovered (without a corresponding code change).
122
+ - Emit ADR for design choices: library, schema, pattern, file layout, hook semantics, API behavior, validation semantics.
123
+ - Do not emit Runbook or Knowledge blocks here. This hook stores MADR-style decision records only.
106
124
 
107
125
  OUTPUT NONE ONLY IF the session is genuinely trivial:
108
126
  - A single typo fix, comment edit, or lint cleanup with no design content
109
- - A pure question/answer with no code change and no surprising fact
127
+ - A pure question/answer with no durable decision
110
128
  - An aborted/empty session
111
129
 
112
- When in doubt, emit a block. Capturing too much is acceptable; missing real work is not.
130
+ When in doubt, emit a conservative ADR with explicit "not discussed" placeholders rather than inventing missing details.
113
131
 
114
132
  TRANSCRIPT FOLLOWS:
115
133
  ---
@@ -160,7 +160,7 @@ PROMPT_HEADER=$(cat <<'EOF'
160
160
  {
161
161
  "operations": [
162
162
  { "op": "delete", "file": "<basename>.md", "reason": "..." },
163
- { "op": "rewrite", "file": "<basename>.md", "slug": "<kebab-case-ukrainian>", "content": "<повний markdown файлу>" },
163
+ { "op": "rewrite", "file": "<basename>.md", "slug": "<kebab-case-ukrainian>", "content": "<повний markdown файлу у MADR 4.0.0>" },
164
164
  { "op": "merge-into", "file": "<basename>.md", "target": "<slug>.md", "additions": "<markdown для дописування>" }
165
165
  ]
166
166
  }
@@ -169,11 +169,15 @@ PROMPT_HEADER=$(cat <<'EOF'
169
169
 
170
170
  1. `delete` — драфт тривіальний / повністю покритий іншим існуючим clean-ADR-ом / порожній. Поясни короткою причиною українською.
171
171
 
172
- 2. `rewrite` — драфт має самостійну цінність. Повертай у `content` повний фінальний вміст файлу:
172
+ 2. `rewrite` — драфт має самостійну цінність як decision record. Повертай у `content` повний фінальний вміст файлу у форматі MADR 4.0.0 minimal:
173
173
  - Без YAML frontmatter (жодного `session:`, `captured:`, `transcript:`).
174
174
  - Заголовок `# <Title>` українською.
175
175
  - Один рядок `**Status:** Accepted` і один рядок `**Date:** YYYY-MM-DD` — дату беремо з поля `captured:` оригінальної чернетки (перші 10 символів ISO-дати).
176
- - Далі розділи **Контекст**, **Рішення/Процедура/Факт**, **Обґрунтування**, **Розглянуті альтернативи**, **Зачіпає** як у драфті, але причесані: цілісні речення, без скорочень, без слідів автогенерованого тегу типу `## Knowledge`.
176
+ - Далі секції з точними MADR headings англійською: `## Context and Problem Statement`, `## Considered Options`, `## Decision Outcome`, `### Consequences`, `## More Information`.
177
+ - У `## Considered Options` перелічуй лише варіанти, які є в драфті/transcript. Якщо альтернатив не було, додай bullet `Інші варіанти в transcript не обговорювалися.`
178
+ - У `## Decision Outcome` використовуй форму `Chosen option: "<option>", because <reason>.` Причина має спиратися на драфт/transcript, без вигаданого business/context.
179
+ - У `### Consequences` пиши bullets `Good, because ...`, `Bad, because ...`, `Neutral, because ...`. Якщо наслідок не зафіксований, явно пиши `transcript не містить підтвердження ...`, не вигадуй.
180
+ - У `## More Information` перенеси файли, команди, публічні API, конфіги й transcript facts. Якщо нема — `Додаткової інформації в transcript не зафіксовано.`
177
181
  - `slug` — kebab-case українською (наприклад `ланцюжок-запуску-abie`, `npm-publish-flow`). Без розширення `.md`. Літери малі, дозволено цифри, дефіс, кирилиця. Якщо тема технічна англійською (назва пакету, ключове слово) — лиши англійською без транслітерації.
178
182
 
179
183
  3. `merge-into` — драфт повторює тему вже існуючого clean-файлу зі списку нижче. `target` — точна назва файлу зі списку (з `.md`). `additions` — лише новий зміст, який варто дописати в кінець target-файлу під підзаголовком `## Update YYYY-MM-DD` (date з `captured` драфта). Якщо нічого нового додати — використовуй `delete`.
@@ -184,6 +188,7 @@ PROMPT_HEADER=$(cat <<'EOF'
184
188
  - Кожен файл з вхідного списку має зʼявитися у `operations` рівно один раз.
185
189
  - Слаги не повторювати між операціями того самого батча. Якщо дві чернетки про одну тему — одна `rewrite`, інша `merge-into target: <slug>.md` з тим самим slug-ом.
186
190
  - Не вигадуй target, якого нема у списку clean-файлів.
191
+ - Не вигадуй альтернативи, decision drivers, наслідки, людей або зовнішній контекст. Якщо даних бракує — явно напиши, що transcript цього не містить.
187
192
 
188
193
  Вхідні драфти і clean-список — нижче.
189
194
  EOF
package/CHANGELOG.md CHANGED
@@ -4,6 +4,67 @@
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
+
62
+ ## [1.13.2] - 2026-05-17
63
+
64
+ ### Changed
65
+
66
+ - **`adr` hook output тепер MADR v4.0.0 minimal** — capture/normalize prompts генерують ADR-и з canonical headings `Context and Problem Statement`, `Considered Options`, `Decision Outcome`, `Consequences`, `More Information`. Prompts стали evidence-bound: якщо transcript не містить альтернатив або підтверджених наслідків, hook явно пише, що даних немає, замість вигадування деталей.
67
+
7
68
  ## [1.13.1] - 2026-05-17
8
69
 
9
70
  ### Added
@@ -14,6 +75,10 @@
14
75
 
15
76
  ### Changed
16
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).
17
82
  - **9 правил переведено з `alwaysApply: true` на `alwaysApply: false` + `globs:`** — AI-контекст у Cursor/Claude Code підвантажується лише при роботі з релевантними файлами; програмна валідація через `npx check <rule>` залишається повністю функціональною незалежно від AI-контексту. Економить контекстне вікно у сесіях, де редагують код, далекий від відповідних конфігів.
18
83
  - **`bun`** (`1.7 → 1.8`) — `globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"`
19
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
- return readFile(bundledPath, 'utf8')
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.1",
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": {
package/rules/adr/adr.mdc CHANGED
@@ -4,9 +4,19 @@ alwaysApply: true
4
4
  version: '2.0'
5
5
  ---
6
6
 
7
- ## Дві фази, один каталог
7
+ ## MADR v4 і дві фази
8
8
 
9
- ADR живуть у єдиному каталозі **`docs/adr/`**. Є два стани файлу, які відрізняються YAML frontmatter:
9
+ ADR живуть у єдиному каталозі **`docs/adr/`**. Clean ADR-и мають формат **MADR v4.0.0 minimal** з точними section headings англійською:
10
+
11
+ - `## Context and Problem Statement`
12
+ - `## Considered Options`
13
+ - `## Decision Outcome`
14
+ - `### Consequences`
15
+ - `## More Information`
16
+
17
+ Вміст секцій — українською, code identifiers / paths / commands — як у transcript. Якщо transcript не містить альтернатив або підтверджених наслідків, hook має явно писати `Інші варіанти в transcript не обговорювалися.` або `transcript не містить підтвердження ...`, а не вигадувати відсутні факти.
18
+
19
+ Є два стани файлу, які відрізняються YAML frontmatter:
10
20
 
11
21
  - **Draft** — файл з frontmatter `session: …`, `captured: …`, `transcript: …` та timestamp-іменем `YYYYMMDD-HHMMSS-<sid>.md`. Пише `capture-decisions.sh` після кожної сесії.
12
22
  - **Clean** — файл без frontmatter, з kebab-case-іменем `<slug>.md` (наприклад `ланцюжок-запуску-abie.md`). Створює `normalize-decisions.sh` або людина руками.
@@ -15,7 +25,7 @@ ADR живуть у єдиному каталозі **`docs/adr/`**. Є два
15
25
 
16
26
  ### Фаза 1 — Capture
17
27
 
18
- Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сесії (через `jq`), витягає текст, `thinking`-блоки та назви `tool_use`-викликів, передає компактний дайджест у LLM CLI з промптом українською і записує результат у **`docs/adr/<timestamp>-<session>.md`**, якщо модель повернула блок з шапкою `## ADR|Runbook|Knowledge …`. Якщо модель повернула `NONE` (тривіальна сесія) — нічого не пишеться. Рекурсію з внутрішнього виклику моделі блокує env-var `CAPTURE_DECISIONS_RUNNING=1`.
28
+ Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сесії (через `jq`), витягає текст, `thinking`-блоки та назви `tool_use`-викликів, передає компактний дайджест у LLM CLI з evidence-bound промптом і записує результат у **`docs/adr/<timestamp>-<session>.md`**, якщо модель повернула MADR-блок з шапкою `## ADR …`. Якщо модель повернула `NONE` (тривіальна сесія) — нічого не пишеться. Рекурсію з внутрішнього виклику моделі блокує env-var `CAPTURE_DECISIONS_RUNNING=1`.
19
29
 
20
30
  Для Cursor payload скрипт бере `transcript_path`, `conversation_id` / `generation_id` і `workspace_roots[0]`; для Claude Code — `transcript_path`, `session_id` і `CLAUDE_PROJECT_DIR`.
21
31
 
@@ -34,7 +44,7 @@ LLM повертає масив операцій:
34
44
  | `op` | Семантика | Поля |
35
45
  | --- | --- | --- |
36
46
  | `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
37
- | `rewrite` | Чернетка стає окремим clean-файлом: frontmatter знімається, ім'я → `<slug>.md`, додаються `**Status: Accepted**` і `**Date:**` з `captured`. | `file`, `slug`, `content` |
47
+ | `rewrite` | Чернетка стає окремим clean-файлом MADR v4 minimal: frontmatter знімається, ім'я → `<slug>.md`, додаються `**Status:** Accepted`, `**Date:**` з `captured` і canonical MADR headings. | `file`, `slug`, `content` |
38
48
  | `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
39
49
 
40
50
  `slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. Колізія slug-ів обробляється детермінованим суфіксом `-2`, `-3`.
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Перевіряє GitHub Actions за правилом ga.mdc.
3
3
  *
4
- * Workflows лише з розширенням `.yml`, наявність clean/lint workflow, конфіг zizmor з ref-pin,
5
- * відсутність MegaLinter, коректний скрипт `lint-ga` у `package.json`, виклик у `lint-ga.yml`,
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`) у Rego-полісі під `npm/policy/ga/` і запускаються через
14
- * `bun run lint-ga` (`runConftestStep` у `lint-ga.mjs`). Тут лишилася лише git-залежна
15
- * перевірка `on.*.paths` glob-ів через `git ls-files :(glob)`.
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,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": "package.json",
5
+ "required": true
6
+ },
7
+ "missingMessage": "package.json не існує — потрібен lint-ga у scripts (ga.mdc)"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": ".vscode/extensions.json",
5
+ "required": true
6
+ },
7
+ "missingMessage": ".vscode/extensions.json не існує — додай github.vscode-github-actions (ga.mdc)"
8
+ }
@@ -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,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": {
4
+ "single": ".github/zizmor.yml",
5
+ "required": true
6
+ },
7
+ "missingMessage": ".github/zizmor.yml не існує — потрібен для zizmor (ga.mdc)"
8
+ }
@@ -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
+ }