@nitra/cursor 1.30.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,42 @@
3
3
  Усі помітні зміни цього модуля документуються тут.
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
- # [1.30.0] - 2026-05-29
6
+
7
+ ## [1.32.0] - 2026-05-30
8
+
9
+ ### Added
10
+
11
+ - **`rules/ci4/js/marksman_config.mjs`** + **`rules/ci4/js/data/marksman_config/marksman.baseline.toml`** — новий JS-концерн `marksman_config` за зразком `test.stryker_config`: при `npx @nitra/cursor fix ci4` копіює canonical `.marksman.toml` baseline у корінь cwd, якщо файлу ще немає. Idempotent через паттерн `ensureBaselineFile` (existsSync → pass+skip vs copyFile → pass+create) — ручні правки користувача між прогонами зберігаються, повторний прогон не перетирає. Baseline містить три ключові опції: `[core] markdown.glfm = true` (GLFM-фічі portable subset — alerts/таблиці/todo), `[completion] wiki.style = "file-stem"` (ADR slug == ім'я файла; стабільний ідентифікатор у AUTOGEN `sources`/manifest/валідаторі — заголовок міняється, посилання не ламається), `[code_action] toc.enable = true` (Insert/Update TOC code action для довгих arc42-сторінок). Дефолти marksman (`title-slug-ref` + вимкнений GLFM) ламали б частину задокументованої навігації. Розміщення в `cwd` (корені репо) робить весь монорепо одним marksman-workspace — README.md і docs/ перехресно навігуються. Тести: `+4` сценарії у `rules/ci4/js/tests/marksman_config.test.mjs` через `withTmpDir` (порожній cwd → файл створюється; idempotency — кастомний контент не перетирається; валідні TOML-секції `[core]`/`[completion]`/`[code_action]`; exit 0 у обох сценаріях). 4/4 PASS через `bunx vitest run rules/ci4/js/tests/marksman_config.test.mjs`.
12
+
13
+ ### Changed
14
+
15
+ - **`rules/ci4/ci4.mdc`** (`version` 3.1 → 3.2) — у секцію «Viewer/editor: Zed + marksman LSP» додано параграф про авто-створення `.marksman.toml` правилом ci4 з посиланням на canonical baseline і поясненням кожної з трьох ключових опцій (glfm/wiki.style/toc.enable). У frontmatter `description` додано пункт про `.marksman.toml`. Дзеркало `.cursor/rules/n-ci4.mdc` синхронізується наступним прогоном `npx @nitra/cursor`.
16
+
17
+ ## [1.31.0] - 2026-05-30
18
+
19
+ ### Added
20
+
21
+ - **`rules/ci4/policy/vscode_extensions/`** — нова policy за зразком text/style-lint/rego/ga/js-lint/graphql/nginx-default-tpl/rust/tauri: `vscode_extensions.rego` (deny якщо рекомендація з template-snippet відсутня в `.vscode/extensions.json`) + `vscode_extensions_test.rego` (5 сценаріїв: canonical, missing marksman, empty recommendations, extra recommendations пропускаються, drift-test що канон керується через `data.template`) + `template/extensions.json.snippet.json` (`{"recommendations": ["arr.marksman"]}`) + `target.json` (single `.vscode/extensions.json`). Призначення — контрибʼютори, що працюють у VSCode/Cursor замість Zed, отримують той самий шар marksman-навігації (cmd+click по `[link](file.md)`/`[[wiki-link]]`, find-references, refactor-перейменування заголовків) через офіційне розширення marksman LSP. Дзеркальна правка у репо: `.vscode/extensions.json` отримав `arr.marksman` у `recommendations`. Тести: 5/5 PASS через `opa test npm/rules/ci4/policy/vscode_extensions/`.
22
+
23
+ ### Changed
24
+
25
+ - **`rules/ci4/ci4.mdc`** (`version` 3.0 → 3.1) — у секцію «Viewer/editor: Zed + marksman LSP» додано підсекцію **«VSCode-альтернатива»** з канонічним JSON-блоком `.vscode/extensions.json` (`recommendations: ["arr.marksman"]`) і посиланням на snippet-файл policy (за стилем `text.mdc`); також згадка про marksman-сумісні редактори (Neovim, Helix, Emacs). У frontmatter `description` додано пункт про VSCode-розширення. Дзеркало `.cursor/rules/n-ci4.mdc` синхронізується наступним прогоном `npx @nitra/cursor`.
26
+
27
+ ### Fixed
28
+
29
+ - **`vitest.config.js`** — git-залежні тести (`rules/changelog/check.test.mjs`, `rules/ga/workflows.test.mjs`) масово таймаутили локально (`23 failed | 13 passed`, 187s; усі фейли — `Test timed out in 5000ms`, не assertion), хоча в CI зелені. Першопричина: глобальний `~/.gitconfig` тестової машини має `trace2.eventtarget=af_unix:stream:~/.git-ai/.../trace2.sock` (tooling `git-ai`), який успадковується tmp-репо в тестах → кожна git-команда під'єднується до Unix-сокета даемона; коли даемон деградований, запис у сокет блокується (~1s/команда не на CPU), а під `pool: 'forks'` десятки паралельних git-операцій × латентність > 5000ms `testTimeout`. Фікс: `env: { GIT_TRACE2_EVENT: '0' }` прибирає trace2-залежність із гарячого шляху тестового git (root-cause), плюс `testTimeout: 20000` як defence-in-depth проти будь-якої залишкової локальної I/O-латентності. Після фіксу `bun run vitest run rules/changelog/` → `36 passed` за ~7s (відтворювано); повний suite — `1248 passed | 2 skipped`. CI не зачеплено (там немає trace2-таргета).
30
+
31
+ ## [1.30.1] - 2026-05-29
32
+
33
+ ### Added
34
+
35
+ - **`rules/test/coverage/tests/coverage.test.mjs`** — три нові тести `/n-coverage-fix`-ітерації, що вбивають усі чотири вцілілі мутанти на `rules/test/coverage/coverage.mjs`. (1) `opts.fix=false → fixSurvivedMutants НЕ викликається` і (2) `opts.fix=true → fixSurvivedMutants викликається` фіксують умовну гілку `if (opts.fix)` (L189) через лог `'✓ Всі мутанти вбиті — доповнення тестів не потрібне'`, який друкує `fixSurvivedMutants` для порожнього `survived[]`. (3) `source 2-ї стрілки містить fix:false` перевіряє джерело захопленої callback-стрілки 2-го `withLock` через `Function.prototype.toString()`: токени `fix` і `false` мають бути присутні, а `fix: true` — заборонений; це поведінково невловимо (`{}` і `{ fix: false }` дають однаковий falsy `opts.fix`), тому інваріант на рівні джерела.
36
+
37
+ ### Fixed
38
+
39
+ - **`CHANGELOG.md`** — заголовок секції `[1.30.0]` піднято з `#` на `##` (Keep a Changelog vN.M.M вимагає H2 для версій). Чек `npm-module.mdc` зчитував h1 як «без версії», знаходив `[1.29.5]` першою і скаржився на розбіжність із `package.json#version "1.30.0"`.
40
+
41
+ ## [1.30.0] - 2026-05-29
7
42
 
8
43
  ### Changed
9
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.30.0",
3
+ "version": "1.32.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
package/rules/ci4/ci4.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: Архітектурна документація продукту — Markdown як джерело істини; рекомендований стек arc42 + Diátaxis + ADR (MADR v4, формат описаний у правилі `adr`) + C4 як набір нотацій; гібридна модель manual + autogen-зон, що регенеруються з accepted ADR; Zed + marksman LSP як viewer без site-generator-а, portable subset (CommonMark + GFM + Mermaid + KaTeX), collapsible engineer-блоки через нативний `<details>`
2
+ description: Архітектурна документація продукту — Markdown як джерело істини; рекомендований стек arc42 + Diátaxis + ADR (MADR v4, формат описаний у правилі `adr`) + C4 як набір нотацій; гібридна модель manual + autogen-зон, що регенеруються з accepted ADR; Zed + marksman LSP як viewer без site-generator-а, portable subset (CommonMark + GFM + Mermaid + KaTeX), collapsible engineer-блоки через нативний `<details>`; рекомендоване VSCode-розширення `arr.marksman` для контрибʼюторів поза Zed; `.marksman.toml` авто-створюється у корені проєкту з canonical baseline
3
3
  alwaysApply: true
4
- version: '3.0'
4
+ version: '3.2'
5
5
  ---
6
6
 
7
7
  Архітектурна документація проєкту живе у Markdown поряд із кодом. Це не довідник «для людей із порталу архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед будь-якою зміною коду. Тому правила нижче — не оформлення, а робочий процес: який стек використовуємо, як зберігаємо рішення, як автоматично перегенеровуємо проекції з ADR і як рендеримо для змішаної аудиторії (менеджери + інженери + ops).
@@ -208,6 +208,18 @@ User Service відповідає за автентифікацію та про
208
208
  }
209
209
  ```
210
210
 
211
+ **`.marksman.toml`** у корені проєкту авто-створюється правилом ci4 при першому `npx @nitra/cursor fix ci4` із canonical baseline ([data/marksman_config/marksman.baseline.toml](./js/data/marksman_config/marksman.baseline.toml)). Ключові опції — `markdown.glfm = true` (потрібен для GFM-alerts/таблиць/todo з portable subset), `[completion] wiki.style = "file-stem"` (ADR slug == ім'я файла, стабільний ідентифікатор у AUTOGEN `sources`/manifest/валідаторі — заголовок змінюється, посилання не ламається), `[code_action] toc.enable = true` (TOC code action для довгих arc42-сторінок). Без явного конфіга marksman використовує `title-slug-ref` і вимкнений GLFM — частина задокументованої навігації працювала б інакше. Ручні правки конфіга не перетираються — `ensureBaselineFile` ідемпотентний.
212
+
213
+ **VSCode-альтернатива.** Контрибʼютори, що працюють у VSCode/Cursor замість Zed, отримують той самий шар навігації через офіційне розширення marksman LSP. Канонічний запис у `.vscode/extensions.json`:
214
+
215
+ ```json title=".vscode/extensions.json"
216
+ {
217
+ "recommendations": ["arr.marksman"]
218
+ }
219
+ ```
220
+
221
+ Канон `recommendations` (substring requirement): [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json). Інші marksman-сумісні редактори (Neovim, Helix, Emacs) налаштовують `marksman` як LSP-сервер за документацією свого редактора — поведінка ідентична (cmd+click по `[link](file.md)`/`[[wiki-link]]`, find-references, refactor-перейменування).
222
+
211
223
  **Portable-only синтаксис.** Усе, що пишемо в `docs/`, обмежене **CommonMark + GFM + Mermaid у fenced code (` ```mermaid `) + KaTeX (`$...$`) + нативний HTML5 `<details>`**. Заборонено:
212
224
 
213
225
  - pymdownx admonitions (`!!! note`, `??? engineer`, `=== "tab"`)
@@ -0,0 +1,27 @@
1
+ # Workspace-маркер marksman LSP. У корені репо робить весь монорепо одним
2
+ # marksman-workspace — README.md і docs/ перехресно навігуються через
3
+ # [text](file.md) і [[wiki-link]]. Файли .mdc не індексуються — marksman
4
+ # дивиться лише на розширення нижче.
5
+ #
6
+ # Створюється правилом ci4 (npx @nitra/cursor fix ci4) із canonical baseline.
7
+ # Ручні правки не перетираються: повторні прогони ідемпотентні (no-op).
8
+
9
+ [core]
10
+ markdown.file_extensions = ["md", "markdown"]
11
+
12
+ # GitHub-Flavored Markdown (таблиці, todo, alerts, math) — наш portable subset.
13
+ markdown.glfm = true
14
+
15
+ [completion]
16
+ # Стиль резолву [[wiki-link]]:
17
+ # "file-stem" → [[oidc-pkce-flow]] резолвиться у docs/adr/oidc-pkce-flow.md
18
+ # "title-slug-ref" → резолв за slugified H1 заголовком
19
+ # Беремо file-stem: ADR-slug == ім'я файла, стабільний ідентифікатор у
20
+ # AUTOGEN sources, manifest, валідаторі. Заголовок може мінятися — посилання
21
+ # не ламається.
22
+ wiki.style = "file-stem"
23
+
24
+ [code_action]
25
+ # Code action "Insert/Update TOC" — корисно для довгих arc42-сторінок
26
+ # (architecture.md з 12 розділами).
27
+ toc.enable = true
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Концерн `marksman_config` правила ci4 (ci4.mdc): копіює canonical
3
+ * `.marksman.toml` baseline у корінь cwd, якщо файлу ще немає.
4
+ *
5
+ * Marksman LSP читає `.marksman.toml` для визначення workspace-роота,
6
+ * GLFM-флага (GitHub-Flavored Markdown), стилю wiki-links і code actions.
7
+ * Дефолти marksman не вмикають GLFM і використовують `title-slug-ref` —
8
+ * але portable subset з ci4.mdc вимагає GLFM (alerts/таблиці/todo) +
9
+ * `file-stem` (ADR slug == ім'я файла). Без явного конфіга частина
10
+ * marksman-функцій працює інакше, ніж задокументовано у правилі.
11
+ *
12
+ * Idempotent: якщо `.marksman.toml` вже існує (навіть з кастомним вмістом)
13
+ * — не перетирається, тільки рапортується факт існування. Ручні правки
14
+ * користувача зберігаються між прогонами.
15
+ *
16
+ * Файл скопійовано в `cwd`, бо marksman визначає workspace-root за
17
+ * розташуванням свого `.marksman.toml`. У корені репо марксман бачить
18
+ * і docs/, і README.md усіх workspaces одним workspace-ом.
19
+ */
20
+ import { existsSync } from 'node:fs'
21
+ import { copyFile } from 'node:fs/promises'
22
+ import { dirname, join, relative } from 'node:path'
23
+ import { fileURLToPath } from 'node:url'
24
+
25
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
26
+
27
+ const HERE = dirname(fileURLToPath(import.meta.url))
28
+ const MARKSMAN_BASELINE_PATH = join(HERE, 'data', 'marksman_config', 'marksman.baseline.toml')
29
+ const MARKSMAN_TARGET_FILENAME = '.marksman.toml'
30
+
31
+ /**
32
+ * @param {string} [cwd] корінь проєкту (default: `process.cwd()` — CLI-сумісність)
33
+ * @returns {Promise<number>} 0 — OK (створено або вже існує), 1 — baseline-файл пакета зламаний
34
+ */
35
+ export async function check(cwd = process.cwd()) {
36
+ const reporter = createCheckReporter()
37
+
38
+ if (!existsSync(MARKSMAN_BASELINE_PATH)) {
39
+ reporter.fail(`canonical baseline не знайдено (${MARKSMAN_BASELINE_PATH}) — перевстанови @nitra/cursor`)
40
+ return reporter.getExitCode()
41
+ }
42
+
43
+ const target = join(cwd, MARKSMAN_TARGET_FILENAME)
44
+ if (existsSync(target)) {
45
+ reporter.pass(`${MARKSMAN_TARGET_FILENAME} існує (${relative(cwd, target)})`)
46
+ return reporter.getExitCode()
47
+ }
48
+
49
+ await copyFile(MARKSMAN_BASELINE_PATH, target)
50
+ reporter.pass(`${MARKSMAN_TARGET_FILENAME} створено з canonical baseline (${relative(cwd, target)}) (ci4.mdc)`)
51
+ return reporter.getExitCode()
52
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
+ "files": { "single": ".vscode/extensions.json" }
4
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["arr.marksman"]
3
+ }
@@ -0,0 +1,12 @@
1
+ # Перевірка `.vscode/extensions.json` для ci4 (ci4.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ package ci4.vscode_extensions
5
+
6
+ import rego.v1
7
+
8
+ deny contains msg if {
9
+ some rec in data.template.snippet.recommendations
10
+ not rec in {r | some r in object.get(input, "recommendations", [])}
11
+ msg := sprintf(".vscode/extensions.json: recommendations має містити %q (ci4.mdc)", [rec])
12
+ }