@nitra/cursor 1.13.12 → 1.13.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@
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.13] - 2026-05-17
8
+
9
+ ### Added
10
+
11
+ - `text` rule: до ланцюжка `lint-text` додано крок `dotenv-linter` (`runDotenvLinter()` у `npm/rules/text/lint/run-dotenv-linter.mjs`). На знайдених `.env*` рекурсивно по проєкту виконується `dotenv-linter fix -r --no-backup --quiet . --exclude node_modules --exclude .envrc`, після чого симетричний `check` для фінальної перевірки. Якщо інструмент відсутній у `PATH` — друкуються підказки встановлення (`brew install dotenv-linter` для macOS).
12
+ - `text.mdc` (canonical + `.cursor/rules/n-text.mdc` mirror) описує новий крок і вимоги до `dotenv-linter` (тільки в `PATH`, **не** у `dependencies`/`devDependencies`).
13
+ - `.cspell.json` — додано слово `envrc` (направлення на direnv-файл, не key=value).
14
+ - Тест `npm/rules/text/lint/run-dotenv-linter.test.mjs` — порожнє дерево, авто-фікс `LowercaseKey`, ігнор `node_modules`/`.envrc`.
15
+
7
16
  ## [1.13.12] - 2026-05-17
8
17
 
9
18
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.12",
3
+ "version": "1.13.13",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
package/rules/ci4/ci4.mdc CHANGED
@@ -1,103 +1,332 @@
1
1
  ---
2
- description: Дизайн проєкту за C4 model (https://c4model.com); Markdown джерело істини про архітектуру, рішення, тести й документацію
2
+ description: Архітектурна документація продукту Markdown як джерело істини; рекомендований стек arc42 + Diátaxis + ADR (MADR v4, формат описаний у правилі `adr`) + C4 як набір нотацій; гібридна модель manual + autogen-зон, що регенеруються з accepted ADR; MkDocs Material як viewer з collapsible engineer-блоками для змішаної аудиторії
3
3
  alwaysApply: true
4
- version: '1.0'
4
+ version: '2.0'
5
5
  ---
6
6
 
7
- C4-діаграми проєкту живуть у Markdown поряд із кодом. Це не довідник «для людей із порталу
8
- архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед
9
- будь-якою зміною коду. Тому правила нижчене оформлення, а робочий процес.
7
+ Архітектурна документація проєкту живе у Markdown поряд із кодом. Це не довідник «для людей із порталу архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед будь-якою зміною коду. Тому правила нижче — не оформлення, а робочий процес: який стек використовуємо, як зберігаємо рішення, як автоматично перегенеровуємо проекції з ADR і як рендеримо для змішаної аудиторії (менеджери + інженери + ops).
8
+
9
+ Формат самих ADR-файлів MADR v4.0.0 minimal описаний у правилі **`adr`** (`npm/rules/adr/adr.mdc`). Тут описано, як ADR використовуються як вхід для архітектурних проекцій, а не як вони створюються.
10
10
 
11
11
  ## Markdown як джерело істини
12
12
 
13
- Уся ключова інформація про проєкт — архітектура (C4), рішення (ADR), тести й документація —
14
- зберігається у `.md`/`.mdc`-файлах і є **офіційним джерелом істини**. Це єдиний спосіб
15
- тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
16
- важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
13
+ Уся ключова інформація про проєкт — архітектура, рішення (ADR), тести й документація — зберігається у `.md`/`.mdc`-файлах і є **офіційним джерелом істини**. Це єдиний спосіб тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
17
14
 
18
15
  ## Специфікація як джерело істини (Spec-as-Source)
19
16
 
20
- У AI-native розробці первинним артефактом, який підтримують інженери, є **специфікація**, а не
21
- кодова база. Код — похідний артефакт, «будівельне риштування», яке агент генерує, верифікує або
22
- повністю відновлює зі специфікації. Якщо поведінка системи має змінитися — **спочатку оновлюємо
23
- специфікацію (C4/ADR/опис компонента), і лише потім** агент генерує відповідний код. Зворотний
24
- порядок («код вже написаний, доку напишемо потім») перетворює специфікацію на декорацію.
17
+ У AI-native розробці первинним артефактом, який підтримують інженери, є **специфікація**, а не кодова база. Код — похідний артефакт, «будівельне риштування», яке агент генерує, верифікує або повністю відновлює зі специфікації. Якщо поведінка системи має змінитися — **спочатку оновлюємо специфікацію (arc42-розділ, ADR, опис компонента), і лише потім** агент генерує відповідний код. Зворотний порядок («код вже написаний, доку напишемо потім») перетворює специфікацію на декорацію.
25
18
 
26
19
  ## Тест на повне відтворення (Rebuild Test)
27
20
 
28
- Документація вважається повною лише тоді, коли проходить бінарний тест: якщо видалити теку
29
- `src/`, відкрити нову LLM-сесію з чистим контекстом і дати агенту доступ лише до `.md`-файлів —
30
- агент має **відтворити робочу кодову базу**. Архітектурні збої (агент не знає структуру тек),
31
- інтеграційні (неописані міжсервісні контракти), падіння юніт-тестів (неописана бізнес-логіка) —
32
- це не «складність задачі», а **прогалини документації**, які треба заповнити у тому ж PR.
21
+ Документація вважається повною лише тоді, коли проходить бінарний тест: якщо видалити теку `src/`, відкрити нову LLM-сесію з чистим контекстом і дати агенту доступ лише до `.md`-файлів — агент має **відтворити робочу кодову базу**. Архітектурні збої (агент не знає структуру тек), інтеграційні (неописані міжсервісні контракти), падіння юніт-тестів (неописана бізнес-логіка) — це не «складність задачі», а **прогалини документації**, які треба заповнити у тому ж PR.
33
22
 
34
23
  ## Ефективність токенів і чистий Markdown
35
24
 
36
- Вікно контексту LLM обмежене, а якість міркування деградує в міру його заповнення (ефект
37
- «Lost in the Middle»). Тому документація **очищається від HTML-розмітки, CSS-класів,
38
- навігаційних обгорток** і всього, що не несе семантичного навантаження. Чистий Markdown замість
39
- HTML економить 80–90 % токенів (≈16 000 → 1 600 на сторінку), що прямо впливає на точність
40
- згенерованого коду і знижує вартість API. Жодних `<div>`/`<span>`/класів у тілі `.md`/`.mdc`.
25
+ Вікно контексту LLM обмежене, а якість міркування деградує в міру його заповнення (ефект «Lost in the Middle»). Тому документація **очищається від HTML-розмітки, CSS-класів, навігаційних обгорток** і всього, що не несе семантичного навантаження. Чистий Markdown замість HTML економить 80–90 % токенів (≈16 000 → 1 600 на сторінку), що прямо впливає на точність згенерованого коду і знижує вартість API. Жодних `<div>`/`<span>`/класів у тілі `.md`/`.mdc`. Єдиний виняток — HTML-коментарі-маркери AUTOGEN-зон, які не рендеряться у viewer і несуть службову інформацію для генератора (`<!-- AUTOGEN:start ... -->`).
41
26
 
42
27
  ## Контекстна незалежність розділів
43
28
 
44
- RAG витягує **фрагменти**, не цілі документи. Тому кожен розділ має сенс **без сусіднього
45
- тексту**. Заборонено: «як було згадано вище», «ця змінна», «попередній метод», «той самий
46
- сервіс». Замість цього — щоразу явно повторюємо назву сутності: «автентифікація OAuth 2.0»,
47
- «функція `calculateTotal()`», «контейнер `api-gateway`». Коли агент отримає лише цей фрагмент,
48
- у нього має бути повний словник для коректної генерації.
29
+ RAG витягує **фрагменти**, не цілі документи. Тому кожен розділ має сенс **без сусіднього тексту**. Заборонено: «як було згадано вище», «ця змінна», «попередній метод», «той самий сервіс». Замість цього — щоразу явно повторюємо назву сутності: «автентифікація OAuth 2.0», «функція `calculateTotal()`», «контейнер `api-gateway`». Коли агент отримає лише цей фрагмент, у нього має бути повний словник для коректної генерації.
49
30
 
50
31
  ## Docs-as-Code
51
32
 
52
- Документація живе у Git поруч із кодом, проходить **той самий Code Review**, версіонується і
53
- автоматично перевіряється у CI (лінтери Markdown, валідатори посилань, `npx @nitra/cursor
54
- check`). Биті посилання й документація, що «не компілюється», — **блокуючий баг**, не
55
- косметика. Це продовження принципу «оновлення синхронно зі змінами» нижче: один PR — код +
56
- схема + ADR + тести.
33
+ Документація живе у Git поруч із кодом, проходить **той самий Code Review**, версіонується і автоматично перевіряється у CI (лінтери Markdown, валідатори посилань, `npx @nitra/cursor check`). Биті посилання й документація, що «не компілюється», — **блокуючий баг**, не косметика.
57
34
 
58
35
  ## Трасування як документація недетермінованої поведінки
59
36
 
60
- Для компонентів, де рішення приймає нейромережа або складна евристика, класичної форми
61
- «вхід X → вихід Y» **недостатньо** — поведінка недетермінована. Джерелом істини стає
62
- **пайплайн логування й трасування**: які інструменти використав агент, який контекст мав,
63
- чому ухвалив це рішення. У C4-компоненті такого типу — посилання на дашборд/storage трасувань
64
- обов'язкове нарівні з посиланнями на тести.
37
+ Для компонентів, де рішення приймає нейромережа або складна евристика, класичної форми «вхід X → вихід Y» **недостатньо** — поведінка недетермінована. Джерелом істини стає **пайплайн логування й трасування**: які інструменти використав агент, який контекст мав, чому ухвалив це рішення. У описі компонента такого типу — посилання на дашборд/storage трасувань обов'язкове нарівні з посиланнями на тести.
38
+
39
+ ## Рекомендований стек
40
+
41
+ Беремо **завжди**:
42
+
43
+ - **arc42** — скелет документа архітектури. 12 розділів із природним розподілом аудиторій: 1-3 (Introduction, Constraints, Context) — менеджерська проза, 5-9 (Building Blocks, Runtime, Deployment, Crosscutting, Decisions) — інженерні. Розділ 9 «Architecture Decisions» — індекс ADR.
44
+ - **Diátaxis** — мета-структура сайту документації: tutorials / how-to / reference / explanation. arc42 живе в `explanation/`. Для більшості продуктів tutorials опціональні; реально потрібні explanation + reference + how-to.
45
+ - **ADR (MADR v4.0.0 minimal)** — append-only event log архітектурних рішень. Формат, фази capture/normalize, Stop-hook автоматика описані у правилі **`adr`**. Clean ADR-файл — `docs/adr/<slug>.md` без frontmatter, з англомовними MADR-заголовками і `**Status:** Accepted` / `**Date:** YYYY-MM-DD` рядками. Це єдине джерело правди для регенерації проекцій.
46
+ - **C4 model** — як набір нотацій для діаграм усередині arc42: контекстна (C4 рівень 1) і контейнерна (рівень 2) у `explanation/architecture.md`, компонентна (рівень 3) — на сторінках компонентів. Не самостійна метамодель, а інструмент візуалізації.
47
+
48
+ Доповнюємо **під domain**:
49
+
50
+ - **DDD Context Maps** — коли є >1 bounded context (типово для multi-agent: agents, orchestration, memory, tooling). Одна Mermaid-діаграма в `explanation/architecture.md`, ховається під `??? engineer`.
51
+ - **EventModeling** — для event-driven систем (агентські pipeline, CQRS, Event Sourcing). Структура: trigger → command → event → read model. Читається менеджером як наратив, інженером — як креслення.
52
+ - **Business Capability Map** — верхньорівневий manager-overview. Autogen через семантичну класифікацію accepted ADR за тематикою.
53
+ - **Wardley Map** — стратегічний артефакт для board/інвесторів. Manual, не autogen. Квартальне оновлення. Живе в `explanation/strategy.md`.
54
+
55
+ **Не використовуємо** для product docs:
56
+
57
+ - 4+1 View Model, Viewpoints & Perspectives — academic/enterprise, дублюють arc42.
58
+ - ISO/IEC/IEEE 42010 — мета-стандарт без шаблонів.
59
+ - UML повністю — беремо точково Mermaid (Sequence, Component, Deployment) всередині arc42.
60
+ - Service Blueprint, Value Stream Mapping, JTBD, Impact Mapping, User Story Mapping — planning/UX інструменти, не docs.
61
+ - EventStorming, Domain Storytelling — workshop-формати; артефакт з них конвертується в EventModeling або Context Map.
62
+
63
+ ## Структура репозиторію
64
+
65
+ ```
66
+ docs/
67
+ ├── adr/
68
+ │ ├── <slug>.md # clean ADR-и (MADR v4 minimal, без frontmatter)
69
+ │ ├── YYYYMMDD-HHMMSS-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
70
+ │ └── index.md # autogen — список accepted ADR за датою
71
+ ├── explanation/ # Diátaxis: чому і що
72
+ │ ├── overview.md # Capability Map (autogen)
73
+ │ ├── architecture.md # arc42 + Context Map + EventModeling + C4 діаграми
74
+ │ ├── strategy.md # Wardley Map (MANUAL)
75
+ │ └── components/ # сторінки на компонент
76
+ ├── reference/ # API, конфіги, схеми
77
+ ├── how-to/ # runbooks, інтеграції
78
+ ├── snapshots/ # періодичні зрізи
79
+ └── .docgen/
80
+ ├── config.yaml
81
+ ├── prompts/ # системні промпти LLM
82
+ └── manifest.json # реєстр зон, ADR-slug → зони
83
+ ```
84
+
85
+ Інших місць для архітектурних схем у репозиторії немає: розпорошення по підтеках сервісів ламає навігацію «від системи вглиб» і робить агентський аналіз перед зміною дорожчим. Структуру drafts і clean ADR у `docs/adr/` створює і підтримує автоматика правила **`adr`** — окремий формат тут не визначаємо.
86
+
87
+ ## ADR як вхід проекцій
88
+
89
+ Регенератор працює **тільки з clean ADR** (`docs/adr/<slug>.md`, без frontmatter, з рядком `**Status:** Accepted`). Drafts (`YYYYMMDD-HHMMSS-*.md`) ігноруються — їх перетворить у clean ADR `normalize-decisions.sh` із правила `adr`.
90
+
91
+ Поля, які регенератор читає з clean ADR:
92
+
93
+ - **Slug** — ім'я файлу без `.md`, використовується як ідентифікатор у маркерах і `manifest.json`.
94
+ - **Status** — рядок `**Status:** Accepted` у тілі. ADR з іншим статусом (або без рядка) у проекції не потрапляє.
95
+ - **Date** — рядок `**Date:** YYYY-MM-DD`, для впорядкування і відображення у проекції.
96
+ - **Heading H1** — перший `#`-заголовок, як назва рішення.
97
+ - **Розділи MADR** — `## Context and Problem Statement`, `## Considered Options`, `## Decision Outcome`, `### Consequences`, `## More Information`. Для проекції зазвичай беруться Decision Outcome + Consequences.
98
+ - **`## Update YYYY-MM-DD`** — apended-секції у тому ж файлі. Враховуються як еволюція рішення в хронологічному порядку.
99
+
100
+ **Маршрутизація ADR → зони** робиться двома способами:
101
+
102
+ 1. **Explicit** — у самій AUTOGEN-зоні в атрибуті `sources` перелічені slug-и: `sources="ланцюжок-запуску-abie,npm-publish-flow"`. Це єдиний робочий механізм у MADR v4 minimal (frontmatter `affects:` поля немає).
103
+ 2. **Discovery** — семантичний індекс: ембеддинги розділів `## Context and Problem Statement` + `## Decision Outcome` кожного ADR; зона декларує тематику, регенератор підтягує top-K релевантних slug-ів. Використовується для оглядових проекцій (`overview.md`, Capability Map).
104
+
105
+ ## Документація як derived state від ADR
106
+
107
+ Документація = **derived state** від ADR. Інженер ніколи не редагує autogen-тексти вручну — тільки через ADR. Якщо потрібно щось виправити в проекції — це означає, що або є accepted ADR, який треба еволюціонувати через `## Update YYYY-MM-DD` (механіка з правила `adr`), або є некоректний промпт у `docs/.docgen/prompts/`, або взагалі потрібен новий ADR.
108
+
109
+ ## Гібрид manual + autogen
110
+
111
+ Реальні проєкти мають legacy docs, написані до появи ADR. Розв'язок — **зони** через HTML-коментарі (виживають у Markdown, не рендеряться у MkDocs):
112
+
113
+ ```markdown
114
+ # User Service
115
+
116
+ User Service відповідає за автентифікацію та профілі.
117
+ *Цей абзац — manual, LLM не чіпає.*
118
+
119
+ <!-- AUTOGEN:start id="user-service-decisions" hash="sha256:a1b2c3..." sources="ланцюжок-запуску-abie,npm-publish-flow,oidc-pkce-flow" -->
120
+ ## Ключові рішення
121
+
122
+ Сервіс працює на Bun runtime [npm-publish-flow]. Автентифікація — OIDC з PKCE
123
+ [oidc-pkce-flow]. Запуск організовано через [ланцюжок-запуску-abie].
124
+ <!-- AUTOGEN:end id="user-service-decisions" -->
125
+
126
+ ## Історичний контекст
127
+ Сервіс виник у 2023 як частина моноліту...
128
+ ```
129
+
130
+ Типи зон:
131
+
132
+ - **`AUTOGEN`** — повністю від ADR, регенерується з нуля. Hash перевіряється строго.
133
+ - **`MANUAL`** (default — усе поза маркерами) — LLM не торкається. Опційно явні маркери `<!-- MANUAL:start id="..." -->` для документації наміру.
134
+ - **`MERGED`** — LLM отримує існуючий manual + ADR, робить узгоджене оновлення. Найризикованіший, на старті уникати.
135
+
136
+ **Backfill legacy:** поступово витягуйте архітектурні знання з manual-текстів у retroactive ADR. Не одразу — тільки коли торкаєтесь області. Природна еволюція: manual → новий accepted ADR (через звичайний capture-flow) → manual замінюється AUTOGEN-зоною з посиланням на slug.
137
+
138
+ ## Manifest з інвертованим індексом
139
+
140
+ `docs/.docgen/manifest.json`:
141
+
142
+ ```json
143
+ {
144
+ "zones": {
145
+ "user-service-decisions": {
146
+ "file": "explanation/components/user-service.md",
147
+ "sources": ["ланцюжок-запуску-abie", "npm-publish-flow", "oidc-pkce-flow"],
148
+ "content_hash": "sha256:...",
149
+ "generated_at": "2026-05-17T10:32:00Z",
150
+ "generator_version": "v3"
151
+ }
152
+ },
153
+ "adr_index": {
154
+ "ланцюжок-запуску-abie": {
155
+ "affects_zones": ["user-service-decisions", "startup-overview"],
156
+ "status": "Accepted",
157
+ "date": "2026-04-12",
158
+ "updates": ["2026-05-03"]
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Інвертований індекс `adr_index` критичний: коли accepted ADR отримує `## Update YYYY-MM-DD`, регенератор миттєво знає, які зони ререндерити без сканування всіх файлів. Hash зони захищає від тихої перезаписки ручних правок: якщо вміст у файлі не збігається з hash у manifest — генератор зупиняється і вимагає рішення людини.
65
165
 
66
- ## Розташування
166
+ ## Цикл оновлення
67
167
 
68
- C4-діаграми проєкту живуть у теці `docs/ci4/` — це **канонічне місце** для всіх рівнів
69
- моделі: `01-context.md`, `02-containers.md`, `03-components.md`, `04-code.md`, плюс
70
- супутні `README.md` (вступ) і `decisions.md` (зведення ADR-впливів на C4). Інших місць
71
- для C4-схем у репозиторії немає: розпорошення по підтеках сервісів ламає навігацію
72
- «від системи вглиб» і робить агентський аналіз перед зміною дорожчим.
168
+ ```
169
+ 1. Інженер/менеджер читає docs/
170
+ 2. Просить LLM: «хочу змінити
171
+ 3. LLM:
172
+ a. знаходить релевантні clean ADR (за slug у sources зон + семантичним пошуком)
173
+ b. формулює draft-рішення (буде підхоплене capture-decisions.sh як ADR-чернетка)
174
+ c. показує impact: які зони зміняться після прийняття
175
+ 4. Після завершення сесії capture-decisions.sh пише draft у docs/adr/
176
+ 5. Коли поріг ADR_NORMALIZE_THRESHOLD досягнуто (або /n-adr-normalize вручну):
177
+ normalize-decisions.sh перетворює draft → clean ADR <slug>.md з **Status:** Accepted
178
+ 6. Регенератор:
179
+ a. читає clean ADR з **Status:** Accepted (інші ігнорує)
180
+ b. для кожної AUTOGEN-зони збирає relevant ADR (sources= + discovery)
181
+ c. перевіряє hash зони — якщо контент != hash, STOP (хтось редагував вручну)
182
+ d. викликає LLM з промптом проекції
183
+ e. замінює вміст між маркерами, оновлює hash і adr_index у manifest
184
+ 7. Валідатор (детермінований, без LLM)
185
+ 8. Git commit з ADR + regenerated docs
186
+ ```
187
+
188
+ **Full rebuild vs incremental:**
189
+
190
+ - **Full rebuild** (рекомендовано на старті): LLM отримує всі clean ADR зі `**Status:** Accepted` + шаблон — видає документ з нуля. Zero drift.
191
+ - **Incremental**: LLM отримує поточний doc + новий ADR (або `## Update`) — видає diff. Дешевше, але дрейф накопичується.
192
+ - **Компроміс**: incremental + періодичний full rebuild (раз на місяць або після N нових ADR).
193
+
194
+ ## MkDocs Material: collapsible engineer-блоки
195
+
196
+ Менеджер читає прозу зверху, інженер клікає `??? engineer` і провалюється глибше. Один документ — дві аудиторії без дублювання.
197
+
198
+ `mkdocs.yml`:
199
+
200
+ ```yaml
201
+ theme:
202
+ name: material
203
+ features:
204
+ - content.code.copy
205
+ - navigation.tabs
206
+
207
+ markdown_extensions:
208
+ - admonition
209
+ - pymdownx.details
210
+ - pymdownx.superfences
211
+ - pymdownx.tabbed: { alternate_style: true }
212
+ - attr_list
213
+
214
+ extra_css:
215
+ - stylesheets/audience.css
216
+ ```
217
+
218
+ Синтаксис у документах:
219
+
220
+ ```markdown
221
+ User Service автентифікує користувачів і керує профілями.
222
+ Підтримуємо OIDC-логін та email/password.
223
+
224
+ ??? engineer "Технічна реалізація автентифікації"
225
+ OIDC-сервер на Fastify з PKCE flow [oidc-pkce-flow].
226
+ Refresh-токени з rotation, TTL 30 днів, Redis storage.
227
+
228
+ Профілі зберігаються в основній БД, кешуються в Redis на 5 хв.
229
+
230
+ ??? ops "Деталі кешу та інвалідації"
231
+ Cache key: `user:profile:{userId}:v2`.
232
+ Інвалідація — NATS подія `user.profile.updated` [user-profile-events].
233
+ ```
234
+
235
+ `???` = згорнуто за замовчуванням, `???+` = розгорнуто.
236
+
237
+ Кастомні audience-styles у `docs/stylesheets/audience.css`:
238
+
239
+ ```css
240
+ .md-typeset details.engineer > summary {
241
+ background-color: rgba(84, 110, 122, 0.1);
242
+ border-color: #546e7a;
243
+ }
244
+ .md-typeset details.ops > summary {
245
+ background-color: rgba(255, 152, 0, 0.1);
246
+ border-color: #ff9800;
247
+ }
248
+ ```
249
+
250
+ ## Промпт для LLM-проекцій
251
+
252
+ Тримати у `docs/.docgen/prompts/projection.md` як артефакт першого класу — версіонується, ревʼюється, тестується на golden-наборі ADR. Скелет:
253
+
254
+ ```
255
+ Ти пишеш документацію для змішаної аудиторії (менеджери + інженери).
256
+
257
+ Структура кожної секції:
258
+ 1. 2-4 речення прози рівня менеджера: що це робить, яку цінність дає,
259
+ як пов'язано з іншими частинами продукту. Без технологій, версій, коду.
260
+
261
+ 2. Технічні деталі — тільки всередині блоків:
262
+ ??? engineer "Описова назва блоку"
263
+ Код, конфіги, версії, посилання на ADR.
264
+
265
+ 3. Operational деталі:
266
+ ??? ops "Що моніторити та як реагувати"
267
+
268
+ Правила:
269
+ - Якщо прибрати всі ??? блоки, текст лишається зв'язним менеджерським документом.
270
+ - Кожен факт з ADR маркуй [<slug>] всередині engineer-блоку — slug це ім'я clean ADR
271
+ без розширення (наприклад `[ланцюжок-запуску-abie]`).
272
+ - Назви блоків — описові («Чому Percona, а не MariaDB»).
273
+ - НЕ виходь за межі <!-- AUTOGEN:start --> ... <!-- AUTOGEN:end -->.
274
+ - ADR без рядка `**Status:** Accepted` ігноруй.
275
+ - Якщо в ADR є `## Update YYYY-MM-DD` секції — враховуй найсвіжіший стан рішення,
276
+ старі формулювання вважай застарілими.
277
+ ```
278
+
279
+ ## Типові поломки LLM і захист
280
+
281
+ | Проблема | Захист |
282
+ | ---------------------------- | --------------------------------------------------------------------------------- |
283
+ | Галюцинація деталей | Правило `[<slug>]` маркера + post-gen linter, що перевіряє існування `docs/adr/<slug>.md` |
284
+ | Втрата `## Update`-еволюції | Pre-process: збирати всі `## Update YYYY-MM-DD` у ADR, передавати у промпт як патчі |
285
+ | Дрейф термінології | `docs/glossary.md` як перший вхід у кожен промпт |
286
+ | Перенасичення контексту | Двостадійна генерація: спочатку summary кожного ADR (кеш), потім проекція |
287
+ | Inconsistency між проекціями | Cross-projection validator порівнює факти між документами |
288
+
289
+ ## Валідатор (обов'язково)
290
+
291
+ Окремий детермінований прохід після регенерації, **без LLM**. Bun-скрипт на ~100-200 рядків, запускається як pre-commit hook:
292
+
293
+ - Усі `[<slug>]` посилання вказують на існуючі файли `docs/adr/<slug>.md` із рядком `**Status:** Accepted`
294
+ - Усі `AUTOGEN:start` мають парний `AUTOGEN:end` з тим самим `id`
295
+ - Hash у `manifest.json` відповідає фактичному контенту зон
296
+ - Жоден `??? engineer` блок не з'являється в manager-only проекціях
297
+ - Якщо в зоні згадано компонент — він є в `docs/glossary.md`
298
+ - ADR без `**Status:** Accepted` не потрапили у проекцію
299
+
300
+ Без валідатора накопичується тихий дрейф: посилання гниють, hash розходиться з контентом, термінологія дрейфує.
73
301
 
74
302
  ## Аналіз перед зміною
75
303
 
76
- Перш ніж вносити зміни, агент **читає відповідні C4-файли**: контекст, контейнери, компоненти
77
- тієї ділянки, до якої входить редагований код. Без цього кроку легко зламати приховані
78
- залежності між контейнерами або «загубити» зовнішню інтеграцію.
304
+ Перш ніж вносити зміни в код, агент **читає відповідні docs**: контекст, контейнери, компоненти тієї ділянки, до якої входить редагований код, плюс accepted ADR за тематикою області (`docs/adr/<slug>.md` зі `**Status:** Accepted`). Без цього кроку легко зламати приховані залежності між сервісами або «загубити» зовнішню інтеграцію, описану в `explanation/architecture.md`.
79
305
 
80
306
  ## Оновлення синхронно зі змінами
81
307
 
82
- Кожна зміна, що впливає на C4-модель — нова інтеграція, новий компонент, перейменування,
83
- зміна напрямку залежності, видалення сервісу — **супроводжується оновленням C4-схеми у тому ж
84
- PR**. C4 не оновлюється «потім» окремою задачею: розсинхрон між кодом і моделлю — найчастіша
85
- причина, чому архітектурні документи перестають читати.
308
+ Кожна зміна, що впливає на архітектуру — нова інтеграція, новий компонент, перейменування, зміна напрямку залежності, видалення сервісу — **супроводжується новим ADR (або `## Update YYYY-MM-DD` у наявному) і регенерацією проекцій у тому ж PR**. Документація не оновлюється «потім» окремою задачею: розсинхрон між кодом і моделлю — найчастіша причина, чому архітектурні документи перестають читати. Capture/normalize flow з правила `adr` забезпечує саму появу ADR — регенерацію проекцій ініціює окремий hook (наприклад, post-merge або `/regen-docs`).
86
309
 
87
310
  ## Зв'язок із ADR
88
311
 
89
- ADR (`docs/adr/`) описує **вплив рішення на C4**: які контейнери/компоненти з'являються,
90
- зникають, змінюють відповідальність. Якщо рішення суттєве — у тілі ADR явно пишемо, які
91
- C4-схеми потрібно оновити, і робимо це у тому ж PR.
312
+ ADR (`docs/adr/<slug>.md`) джерело правди для autogen-проекцій. Маршрутизація «ADR → зони, на які впливає» виконується через `sources="<slug>,<slug>"` атрибут у самій AUTOGEN-зоні та через семантичний discovery (ембеддинги розділів `## Context and Problem Statement` + `## Decision Outcome`). Поля `affects.components` у frontmatter немає за визначенням MADR v4 minimal — маршрутизація навмисно винесена у зону, а не вшита в ADR, щоб ADR лишався читабельним як автономний документ.
92
313
 
93
314
  ## Зв'язок із тестами
94
315
 
95
- Кожен C4-компонент має **посилання на відповідні тести** — інтеграційні, e2e або юніт залежно
96
- від рівня. Так перехід «компонент → як його перевіряємо» займає один клік, а не пошук по
97
- репозиторію.
316
+ Кожен опис компонента в `explanation/components/<name>.md` має **посилання на відповідні тести** — інтеграційні, e2e або юніт залежно від рівня. Так перехід «компонент → як його перевіряємо» займає один клік, а не пошук по репозиторію. Для агентських компонентів додаткове посилання на storage трасувань — нарівні з тестами, бо без них недетермінована поведінка непідтверджена.
98
317
 
99
318
  ## Зв'язок із документацією
100
319
 
101
- C4-схеми — частина **користувацької документації**, а не закритий артефакт для команди.
102
- Контекстна діаграма (рівень 1) і контейнерна (рівень 2) живуть там, де читач шукає вступ у
103
- проєкт, а не у відокремленій теці «for-architects».
320
+ Архітектурні артефакти — частина **користувацької документації**, а не закритий артефакт для команди. Контекстна діаграма (C4 рівень 1) і контейнерна (рівень 2) живуть там, де читач шукає вступ у проєкт — у `explanation/architecture.md`, не у відокремленій теці «for-architects». MkDocs Material рендерить це з collapsible-блоками, тому менеджер бачить прозу, інженер провалюється в деталі.
321
+
322
+ ## Інструменти
323
+
324
+ - **Claude Code** як runner — slash-команда `/regen-docs` або post-commit hook на зміни `docs/adr/**`
325
+ - **capture-decisions.sh** + **normalize-decisions.sh** — Stop-hooks створення ADR (керує правило `adr`)
326
+ - **gray-matter** на Bun — лише для парсингу draft frontmatter; clean ADR парситься regexp по `**Status:**` / `**Date:**`
327
+ - **MkDocs + Material** як viewer
328
+ - **Mermaid** для C4-діаграм, EventModeling, Context Maps у проекціях
329
+
330
+ ## Query mode як бонус
331
+
332
+ Поверх проекцій — інтерактивний RAG над clean ADR. Інженер питає: «чому ми обрали Percona замість MariaDB?». LLM шукає по semantic index `Context and Problem Statement + Decision Outcome`, повертає `docs/adr/<slug>.md` з прямою цитатою. Корпус малий (десятки-сотні clean ADR після normalize) і структурований (фіксовані MADR-заголовки) — дешево й точно, без галюцинацій.
@@ -2,18 +2,20 @@
2
2
  * CLI-обгортка над канонічним `lint-text` (text.mdc): послідовно
3
3
  * 1) `cspell .` — перевірка правопису з `@nitra/cspell-dict`;
4
4
  * 2) `runShellcheckText()` — авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
5
- * 3) `bunx markdownlint-cli2 --fix "**\/*.md" "**\/*.mdc"` авто-фікс Markdown;
6
- * 4) `runV8rWithGlobs()` schema-валідація json/json5/yaml/yml/toml через v8r з каталогом `@nitra/cursor`.
5
+ * 3) `runDotenvLinter()` авто-фікс і фінальна перевірка `.env*` через `dotenv-linter`;
6
+ * 4) `bunx markdownlint-cli2 --fix "**\/*.md" "**\/*.mdc"` авто-фікс Markdown;
7
+ * 5) `runV8rWithGlobs()` — schema-валідація json/json5/yaml/yml/toml через v8r з каталогом `@nitra/cursor`.
7
8
  *
8
9
  * Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
9
10
  * Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
10
11
  */
11
12
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
13
+ import { runDotenvLinter } from './run-dotenv-linter.mjs'
12
14
  import { runShellcheckText } from './run-shellcheck.mjs'
13
15
  import { runV8rWithGlobs } from './run-v8r.mjs'
14
16
 
15
17
  /**
16
- * Виконує канонічний `lint-text`: cspell → run-shellcheck → markdownlint-cli2 → run-v8r.
18
+ * Виконує канонічний `lint-text`: cspell → run-shellcheck → run-dotenv-linter → markdownlint-cli2 → run-v8r.
17
19
  * Першу помилку повертаємо як код виходу; наступні кроки не запускаються.
18
20
  * Усі кроки синхронні (`spawnSync` + sync-ентрі з пакета), тому функція не async.
19
21
  * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
@@ -26,6 +28,10 @@ export function runLintTextCli() {
26
28
  const shellcheckCode = runShellcheckText()
27
29
  if (shellcheckCode !== 0) return shellcheckCode
28
30
 
31
+ console.log('\n▶ dotenv-linter (авто-фікс + фінальна перевірка .env*)')
32
+ const dotenvCode = runDotenvLinter()
33
+ if (dotenvCode !== 0) return dotenvCode
34
+
29
35
  const markdownlintCode = runLintStep('markdownlint', 'bunx', ['markdownlint-cli2', '--fix', '**/*.md', '**/*.mdc'])
30
36
  if (markdownlintCode !== 0) return markdownlintCode
31
37
 
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Запуск dotenv-linter у ланцюжку lint-text: спочатку авто-фікс, потім фінальна перевірка.
3
+ *
4
+ * dotenv-linter — швидкий лінтер для `.env`-файлів (LowercaseKey, DuplicatedKey, IncorrectDelimiter,
5
+ * UnorderedKey тощо). Інструмент очікується у PATH і **не** додається в `dependencies`/`devDependencies`
6
+ * (аналогічно shellcheck). Якщо `dotenv-linter` відсутній — друкуємо підказки встановлення
7
+ * (`brew install dotenv-linter` на macOS) і повертаємо 1.
8
+ *
9
+ * Файли шукає сам `dotenv-linter` у режимі `-r` (рекурсивно по дереву проєкту). Виключаємо
10
+ * `node_modules` і `.envrc` (direnv shell-синтаксис, не key=value формат). `.bak`-файли інструмент
11
+ * ігнорує самостійно. Якщо `.env*`-файлів немає, dotenv-linter повертає 0 ("Nothing to check").
12
+ *
13
+ * Авто-фікс — один прогон `dotenv-linter fix -r --no-backup --quiet . --exclude …` (інструмент сам
14
+ * застосовує усі виправлення без потреби в diff/patch, на відміну від shellcheck). Після цього —
15
+ * фінальний `dotenv-linter check -r --quiet . --exclude …`; будь-яке залишкове порушення — ненульовий
16
+ * код виходу.
17
+ */
18
+ import { spawnSync } from 'node:child_process'
19
+ import { resolve } from 'node:path'
20
+
21
+ import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
22
+ import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
23
+
24
+ /** Каталоги/файли, які виключаємо з рекурсивного сканування dotenv-linter. */
25
+ const EXCLUDED_PATHS = ['node_modules', '.envrc']
26
+
27
+ /**
28
+ * Друкує підказки встановлення dotenv-linter у stderr.
29
+ * @returns {void}
30
+ */
31
+ function printDotenvLinterInstallHints() {
32
+ process.stderr.write(
33
+ [
34
+ '❌ dotenv-linter не знайдено в PATH.',
35
+ 'Встанови інструмент і повтори lint-text:',
36
+ ' macOS: brew install dotenv-linter',
37
+ ' Linux: curl -sSfL https://git.io/JLbXn | sh -s -- -b /usr/local/bin',
38
+ ' cargo: cargo install dotenv-linter',
39
+ ''
40
+ ].join('\n')
41
+ )
42
+ }
43
+
44
+ /**
45
+ * Будує перелік аргументів `--exclude <path>` для dotenv-linter.
46
+ * @returns {string[]} плоский масив `['--exclude', 'node_modules', '--exclude', '.envrc']`
47
+ */
48
+ function buildExcludeArgs() {
49
+ return EXCLUDED_PATHS.flatMap(p => ['--exclude', p])
50
+ }
51
+
52
+ /**
53
+ * Запускає dotenv-linter з авто-фіксом і фінальною перевіркою.
54
+ * @param {string} [cwd] робочий каталог (за замовчуванням `process.cwd()`)
55
+ * @returns {number} 0 — OK; 1 — інструмент відсутній або є залишкові порушення
56
+ */
57
+ export function runDotenvLinter(cwd = process.cwd()) {
58
+ const root = resolve(cwd)
59
+ const bin = resolveCmd('dotenv-linter')
60
+ if (!bin) {
61
+ printDotenvLinterInstallHints()
62
+ return 1
63
+ }
64
+
65
+ const exclude = buildExcludeArgs()
66
+ const fixRun = spawnSync(bin, ['fix', '-r', '--no-backup', '--quiet', ...exclude, '.'], {
67
+ cwd: root,
68
+ encoding: 'utf8',
69
+ env: process.env,
70
+ stdio: ['ignore', 'pipe', 'pipe']
71
+ })
72
+ if (fixRun.error) {
73
+ process.stderr.write(`${fixRun.error.message}\n`)
74
+ return 1
75
+ }
76
+
77
+ const checkRun = spawnSync(bin, ['check', '-r', '--quiet', ...exclude, '.'], {
78
+ cwd: root,
79
+ encoding: 'utf8',
80
+ env: process.env,
81
+ stdio: ['ignore', 'pipe', 'pipe']
82
+ })
83
+ if (checkRun.error) {
84
+ process.stderr.write(`${checkRun.error.message}\n`)
85
+ return 1
86
+ }
87
+ if (checkRun.status === 0) return 0
88
+ if (checkRun.stdout?.length) process.stdout.write(checkRun.stdout)
89
+ if (checkRun.stderr?.length) process.stderr.write(checkRun.stderr)
90
+ return 1
91
+ }
92
+
93
+ if (isRunAsCli()) {
94
+ process.exitCode = runDotenvLinter()
95
+ }
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: Обробка та перевірка текстових файлів, oxfmt, cspell, shellcheck (sh), markdownlint-cli2, v8r, CI
2
+ description: Обробка та перевірка текстових файлів, oxfmt, cspell, shellcheck (sh), dotenv-linter (.env*), markdownlint-cli2, v8r, CI
3
3
  alwaysApply: true
4
- version: '1.26'
4
+ version: '1.27'
5
5
  ---
6
6
 
7
- **oxfmt** (`.oxfmtrc.json`, редактор), **cspell**, **shellcheck** (tracked `*.sh` у `lint-text`), **markdownlint-cli2**, **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint** / **timonwong.shellcheck**, workflow **`lint-text`**.
7
+ **oxfmt** (`.oxfmtrc.json`, редактор), **cspell**, **shellcheck** (tracked `*.sh` у `lint-text`), **[dotenv-linter](https://dotenv-linter.github.io/)** (`.env*` у `lint-text`), **markdownlint-cli2**, **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint** / **timonwong.shellcheck**, workflow **`lint-text`**.
8
8
 
9
9
  ```json title=".vscode/extensions.json"
10
10
  {
@@ -115,6 +115,8 @@ version: '1.26'
115
115
 
116
116
  **shellcheck:** інструмент лише в **`PATH`** (як у **ga.mdc** для `lint-ga`), **не** додавай до `dependencies` / `devDependencies`. Якщо `shellcheck` відсутній — встанови локально (**macOS:** `brew install shellcheck`; **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`; **Arch:** `sudo pacman -S shellcheck`). Канонічний **`lint-text`** делегує до CLI **`n-cursor lint-text`** (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`): після **`cspell .`** виконується **`runShellcheckText()`** з пакета — циклічно **`shellcheck -f diff`** і **`patch -p1`** для авто-виправлень, потім повний прогін **`shellcheck`** по всіх tracked `*.sh` (у git) або по `**/*.sh` без `node_modules`. Потрібен також **`patch`** у `PATH` (на macOS зазвичай уже є).
117
117
 
118
+ **dotenv-linter:** після shellcheck CLI запускає **`runDotenvLinter()`** з пакета — рекурсивно по `.env*`-файлах проєкту через **`dotenv-linter fix -r --no-backup --quiet . --exclude node_modules --exclude .envrc`**, далі такий самий **`check`** для фінальної перевірки. Інструмент має бути в **`PATH`** і **не** додається в `dependencies` / `devDependencies`. Якщо `dotenv-linter` відсутній — встанови локально (**macOS:** `brew install dotenv-linter`; **Linux:** `curl -sSfL https://git.io/JLbXn | sh -s -- -b /usr/local/bin`; через **cargo:** `cargo install dotenv-linter`).
119
+
118
120
  У v8r **немає** прапорця тихого режиму; CLI-обгортка **`n-cursor lint-text`** на четвертому кроці викликає **`runV8rWithGlobs()`** з пакета (реалізація — `npm/rules/text/js/run-v8r.mjs`): під капотом послідовні **`bunx v8r`** для кожного типу (**json**, **json5**, **yml**, **yaml**, **toml**), бо один процес v8r з кількома глобами падає з **98**, якщо хоч один glob порожній, і тоді інші розширення не перевіряються. Вивід при кодах **0** і **98** не показується. Каталог схем **`schemas/v8r-catalog.json`** пакета `@nitra/cursor` обгортка підставляє в v8r сама.
119
121
 
120
122
  ```json title="package.json"
@@ -9,12 +9,16 @@ version: '1.1'
9
9
 
10
10
  Скіл прибирає з проєкту все, що належить **ru-середовищу**. Залишаються тільки `dev` (як база) та `ua` як активне продакшн-середовище. Працюй послідовно по секціях нижче — після кожної секції перевіряй, що проєкт лишається консистентним (`kustomization.yaml` посилається лише на наявні файли, GitHub Actions-вирази синтаксично коректні).
11
11
 
12
+ ## 0. Що НЕ чіпати
13
+
14
+ **`node_modules/`** (і будь-які `**/node_modules/`) — повністю виключаються з аналізу й модифікацій. Це згенеровані залежності: збіги на `ru`, `values-ru.*`, `cr.yandex` тощо там нерелевантні і відновляться при наступному `install`. Усі `find` / `git grep` / автозаміни мають пропускати ці шляхи. Аналогічно не чіпай `.git/`, `dist/`, `build/`, `.next/`, `.nuxt/`, `.output/`, `coverage/`.
15
+
12
16
  ## 1. Директорії з назвою `ru`
13
17
 
14
- Видали всі директорії з назвою `ru` у проєкті:
18
+ Видали всі директорії з назвою `ru` у проєкті (крім тих, що всередині `node_modules`):
15
19
 
16
20
  ```bash
17
- find . -type d -name "ru" -exec rm -rf {} +
21
+ find . -type d \( -name node_modules -o -name .git \) -prune -o -type d -name "ru" -print -exec rm -rf {} +
18
22
  ```
19
23
 
20
24
  Це чистить як `country/ru/`, так і `k8s/<...>/ru/` (overlay у kustomize). Після видалення overlay `ru/` обов’язково прибери відповідний запис у `resources:` у батьківському `kustomization.yaml`, якщо він там лишився.
@@ -27,7 +31,8 @@ find . -type d -name "ru" -exec rm -rf {} +
27
31
  - будь-які файли, що закінчуються на `-ru` або `-ru.<ext>`, наприклад `site/.env.prod-ru`, `*.env.prod-ru`, `*.prod-ru.conf`
28
32
 
29
33
  ```bash
30
- find . -type f \( -name "values-ru.*" -o -name "*-ru" -o -name "*.prod-ru" -o -name "*.prod-ru.*" \) -delete
34
+ find . -type d \( -name node_modules -o -name .git \) -prune -o \
35
+ -type f \( -name "values-ru.*" -o -name "*-ru" -o -name "*.prod-ru" -o -name "*.prod-ru.*" \) -print -delete
31
36
  ```
32
37
 
33
38
  ## 3. `.github/workflows/*.yml`
@@ -194,5 +199,5 @@ const tr = {
194
199
  ## 6. Після очистки
195
200
 
196
201
  - Переконайся, що `kustomization.yaml` у кожній директорії `k8s/` не посилається на видалені overlay або файли.
197
- - Пройдись `git grep` по репозиторію на залишки: `git grep -n -i -e '\bru\b' -e cr\.yandex -e country/ru -e prod-ru -e values-ru -e "'ya'"` — переглянь усі знахідки вручну, бо `ru` як слово може траплятися в легітимних контекстах (наприклад, `truncate`, `Aurum`, `cruft`). Видаляй лише ті входження, що належать ru-середовищу.
202
+ - Пройдись `git grep` по репозиторію на залишки: `git grep -n -i -e '\bru\b' -e cr\.yandex -e country/ru -e prod-ru -e values-ru -e "'ya'"` — переглянь усі знахідки вручну, бо `ru` як слово може траплятися в легітимних контекстах (наприклад, `truncate`, `Aurum`, `cruft`). Видаляй лише ті входження, що належать ru-середовищу. `git grep` за замовчуванням пропускає невідстежувані шляхи, тож `node_modules/` у вихід не потрапить, поки воно у `.gitignore`.
198
203
  - Перевір CI локально: `npx @nitra/cursor check abie` (якщо правило **abie** ввімкнене у проєкті).