@nitra/cursor 12.8.4 → 12.8.6

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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +8 -7
  3. package/package.json +1 -1
  4. package/rules/abie/js/env_dns.mdc +33 -0
  5. package/rules/abie/js/firebase_hosting.mdc +3 -0
  6. package/rules/abie/js/hc_pairing.mdc +23 -0
  7. package/rules/abie/js/http_route_base.mdc +25 -0
  8. package/rules/abie/js/ua_http_route.mdc +47 -0
  9. package/rules/abie/js/ua_node_selector.mdc +27 -0
  10. package/rules/abie/main.mdc +11 -127
  11. package/rules/doc-files/js/docs/index.md +15 -15
  12. package/rules/js/docs/main.md +6 -6
  13. package/rules/js/js/docs/check.md +11 -10
  14. package/rules/js/js/docs/tooling.md +7 -7
  15. package/rules/js/js/docs/utils_imports.md +13 -12
  16. package/rules/test/js/docs/stryker_config.md +12 -12
  17. package/rules/test/js/docs/vitest-config-pool-forks.md +13 -7
  18. package/scripts/auto-rules.mjs +6 -6
  19. package/scripts/auto-skills.mjs +3 -3
  20. package/scripts/docs/auto-rules.md +17 -31
  21. package/scripts/docs/auto-skills.md +18 -163
  22. package/scripts/docs/sync-setup-bun-deps-action.md +6 -5
  23. package/scripts/lib/docs/inline-template-links.md +13 -293
  24. package/scripts/lib/docs/mirror-parity.md +9 -9
  25. package/scripts/lib/docs/rule-meta.md +12 -12
  26. package/scripts/lib/docs/skill-meta.md +9 -9
  27. package/scripts/lib/docs/timing-summary.md +6 -6
  28. package/scripts/lib/docs/worktree-notice.md +10 -8
  29. package/scripts/lib/inline-template-links.mjs +31 -0
  30. package/scripts/lib/mirror-parity.mjs +5 -3
  31. package/scripts/lib/rule-meta.mjs +6 -6
  32. package/scripts/lib/skill-meta.mjs +6 -6
  33. package/scripts/lib/worktree-notice.mjs +2 -2
  34. package/scripts/utils/docs/resolve-js-root.md +4 -4
  35. package/types/bin/n-cursor.d.ts +1 -1
@@ -3,305 +3,25 @@ type: JS Module
3
3
  title: inline-template-links.mjs
4
4
  resource: npm/scripts/lib/inline-template-links.mjs
5
5
  docgen:
6
- crc: 7726f0ef
6
+ crc: b659349c
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
7
9
  ---
8
10
 
9
- Модуль `inline-template-links.mjs` — допоміжна утиліта для **build-кроку обробки `.mdc`-правил**. Її роль: у текстовому вмісті `.mdc`-документа знайти Markdown-посилання, які ведуть на template-файли (шлях містить сегмент `/template/` або `/templates/`), і **замінити** ці посилання на **інлайн fenced-блоки** з фактичним вмістом target-файлу.
11
+ ## Огляд
10
12
 
11
- Простими словами: замість того щоб у згенерованому правилі читач бачив посилання `[конфіг](./templates/package.json.snippet.json)`, він побачить безпосередньо назву реального файлу (`package.json`) і fenced-блок із його вмістом це робить правило «самодостатнім», без потреби клікати по лінках.
13
+ Цей модуль реалізує механізми вбудовування контенту в текстові документи, використовуючи конфігурації з `package.json.snippet.json` та `package.json`. Він замінює посилання в тексті на вміст файлів-шаблонів, розташованих у каталозі правил, а також на вміст файлів з розширенням .mdc, які не є шаблонами. Функції дозволяють вставляти посилання на шаблони (`inlineTemplateLinks`) та включати вміст Markdown (`inlineMarkdownIncludes`).
12
14
 
13
- Особливості:
15
+ ## Поведінка
14
16
 
15
- - Працює асинхронно (`async`), бо читає файли через `node:fs/promises`.
16
- - Робить **fail-loud** валідацію: якщо посилання вказує на неіснуючий файл кидає `Error` (а не мовчки пропускає), щоб автор правила одразу побачив проблему.
17
- - «Розгортає» спеціальні суфікси `.snippet.<ext>` / `.deny.<ext>` / `.contains.<ext>` до імені реального target-файлу, який вони описують (наприклад `package.json.snippet.json` → `package.json`).
18
- - Безпечно щодо ReDoS: усі regexp — статичні літерали з обмеженням довжини, без `new RegExp(variable)` із користувацьких даних.
17
+ inlineTemplateLinks замінює посилання в тексті на вбудовані блоки з вмістом файлів, що містять шаблони, якщо ці файли знаходяться в каталозі правил.
18
+ inlineMarkdownIncludes замінює посилання в тексті на вміст файлів з розширенням .mdc, якщо ці файли не є шаблонами.
19
19
 
20
- Модуль експортує єдину функцію `inlineTemplateLinks(text, ruleDir)`.
20
+ ## Публічний API
21
21
 
22
- ## Експорти / API
22
+ inlineTemplateLinks Замінює посилання у Markdown, що містять `/template/`, на вбудовані блокові конструкції, зчитуючи вміст з вказаного файлу. Викидає помилку, якщо цільове посилання не знайдено.
23
+ inlineMarkdownIncludes — Замінює посилання у Markdown, що закінчуються на `.mdc` (і не є шляхом `/template/`), на сирий вміст відповідного файлу Markdown. Викидає помилку, якщо цільове посилання не знайдено.
23
24
 
24
- | Експорт | Тип | Призначення |
25
- | --------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
26
- | `inlineTemplateLinks` | `async function(text: string, ruleDir: string): Promise<string>` | Замінює Markdown-посилання на template-файли в `.mdc`-тексті на інлайн fenced-блоки з фактичним вмістом цих файлів. |
25
+ ## Гарантії поведінки
27
26
 
28
- Усі інші імена в модулі (`langFromExt`, `normalizeTargetName`, константи `MD_LINK_RE`, `TEMPLATE_SEGMENT_RE`, `SLOT_SUFFIX_RES`) — **внутрішні** (не експортуються).
29
-
30
- ## Внутрішні константи
31
-
32
- ### `MD_LINK_RE`
33
-
34
- ```js
35
- ;/\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
36
- ```
37
-
38
- Глобальний regexp, який ловить **Markdown-посилання вигляду `[label](./path)`** із обов'язковим префіксом `./` у href. Group 1 — текст посилання (до 200 символів), group 2 — шлях (до 500 символів, що починається з `./`). Обмеження довжин — захист від ReDoS / pathological input.
39
-
40
- ### `TEMPLATE_SEGMENT_RE`
41
-
42
- ```js
43
- ;/\/templates?\//
44
- ```
45
-
46
- Перевіряє, чи шлях містить сегмент `/template/` або `/templates/`. Тільки такі посилання вважаються «template-посиланнями» і підлягають заміні; інші Markdown-лінки залишаються недоторканими.
47
-
48
- ### `SLOT_SUFFIX_RES`
49
-
50
- Масив із трьох **статичних** regexp:
51
-
52
- ```js
53
- ;[/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
54
- ```
55
-
56
- Кожен ловить ім'я файлу з суфіксом-«слотом»: `<name>.snippet.<ext>`, `<name>.deny.<ext>`, `<name>.contains.<ext>`. Group 1 — це ім'я реального target-файлу (без суфікса слоту і без власного розширення). Коментар у коді явно зазначає: regexp-літерали статичні, без `RegExp(variable)`.
57
-
58
- ## Функції
59
-
60
- ### `langFromExt(filePath)` — internal
61
-
62
- Сигнатура:
63
-
64
- ```js
65
- function langFromExt(filePath: string): string
66
- ```
67
-
68
- Параметри:
69
-
70
- - `filePath` — рядок зі шляхом до файлу (досить навіть базового імені, бо використовується лише розширення).
71
-
72
- Повертає:
73
-
74
- - Рядок-ідентифікатор мови для Markdown fenced-блока:
75
- - `'json'` — якщо розширення `.json`;
76
- - `'toml'` — якщо `.toml`;
77
- - `'yaml'` — якщо `.yml` або `.yaml`;
78
- - `''` (порожній рядок) — для будь-яких інших розширень.
79
-
80
- Side effects: жодних — чиста функція над рядком.
81
-
82
- Призначення: визначити, який мовний таг ставити після відкривального ` ``` ` у згенерованому fenced-блоці, щоб підсвічування синтаксису працювало коректно.
83
-
84
- ### `normalizeTargetName(fileBasename)` — internal
85
-
86
- Сигнатура:
87
-
88
- ```js
89
- function normalizeTargetName(fileBasename: string): string
90
- ```
91
-
92
- Параметри:
93
-
94
- - `fileBasename` — базове ім'я файлу (без шляху), наприклад `package.json.snippet.json`.
95
-
96
- Повертає:
97
-
98
- - Якщо ім'я **збігається з одним із regexp у `SLOT_SUFFIX_RES`** (тобто має суфікс `.snippet.<ext>`, `.deny.<ext>` або `.contains.<ext>`) — повертається **group 1** першого збігу (ім'я без слоту). Приклади:
99
- - `package.json.snippet.json` → `package.json`
100
- - `eslint.config.js.deny.js` → `eslint.config.js`
101
- - `Caddyfile.contains.txt` → `Caddyfile`
102
- - Якщо жоден з regexp не збігся — повертається оригінальне `fileBasename` без змін.
103
-
104
- Side effects: відсутні.
105
-
106
- Призначення: для template-файлу з суфіксом-слотом відновити **реальне ім'я target-файлу**, на який цей template посилається; саме це ім'я потім підставляється як заголовок перед fenced-блоком у згенерованому Markdown.
107
-
108
- Коментар над функцією у вихіднику прямо описує цю поведінку: «Strip `.<slot>.<ext>` suffix (slot ∈ snippet/deny/contains) to recover the real target file name».
109
-
110
- ### `inlineTemplateLinks(text, ruleDir)` — **exported**
111
-
112
- Сигнатура:
113
-
114
- ```js
115
- export async function inlineTemplateLinks(
116
- text: string,
117
- ruleDir: string,
118
- ): Promise<string>
119
- ```
120
-
121
- Параметри:
122
-
123
- - `text` — вміст `.mdc`-файлу (повний текст) як рядок.
124
- - `ruleDir` — **абсолютний** шлях до директорії правила (наприклад `.../npm/rules/security/`). Усі відносні href із `./` резолвляться **відносно цього каталогу**.
125
-
126
- Повертає:
127
-
128
- - `Promise<string>` — трансформований текст, у якому всі **template-посилання** замінено на блоки виду:
129
-
130
- ````text
131
- `<targetName>`:
132
-
133
- ```<lang>
134
- <contents>
135
- ````
136
-
137
- ```
138
-
139
- де `targetName` — результат `normalizeTargetName(basename(absPath))`, `lang` — результат `langFromExt(absPath)`, а `contents` — вміст файлу після `.trim()`.
140
-
141
- ```
142
-
143
- - Якщо у тексті немає жодного template-посилання — повертається **той самий `text` без змін** (early-exit).
144
-
145
- Алгоритм роботи:
146
-
147
- 1. Знаходимо **всі** збіги `MD_LINK_RE` у `text` через `text.matchAll(...)`.
148
- 2. Фільтруємо їх: залишаємо лише ті, у яких href (group 2) містить `/template/` або `/templates/` (через `TEMPLATE_SEGMENT_RE.test(m[2])`).
149
- 3. Якщо після фільтрації збігів **немає** — повертаємо `text` як є.
150
- 4. Кладемо стартовий результат `result = text`.
151
- 5. Для кожного збігу послідовно (`for ... of`, з `await` на читанні файлу):
152
- 1. Деструктуруємо: `const [fullMatch, , href] = match` (label не використовується, тому позиція пропущена).
153
- 2. Будуємо відносний шлях: `relPath = href.slice(2)` — обрізаємо префікс `./` (його гарантує regexp).
154
- 3. Збираємо абсолютний шлях: `absPath = join(ruleDir, relPath)`.
155
- 4. Перевіряємо існування: `existsSync(absPath)`. Якщо файлу немає — **кидаємо** `Error`:
156
-
157
- ```text
158
- inlineTemplateLinks: file not found: <absPath> (referenced from .mdc)
159
- ```
160
-
161
- Жодного fallback / тихого пропуску — це fail-loud за дизайном.
162
-
163
- 5. Читаємо файл: `raw = await readFile(absPath, 'utf8')`, далі `contents = raw.trim()` (прибираємо хвостові пробіли / переноси).
164
- 6. Обчислюємо `lang = langFromExt(absPath)`.
165
- 7. Обчислюємо `targetName = normalizeTargetName(basename(absPath))`.
166
- 8. Формуємо `replacement` — backtick-екранований заголовок, порожній рядок і fenced-блок із `lang`:
167
-
168
- ```js
169
- ;`\`${targetName}\`:\n\n\`\`\`${lang}\n${contents}\n\`\`\``
170
- ```
171
-
172
- 9. Робимо заміну: `result = result.replace(fullMatch, () => replacement)`. Передача **callback-форми** в `.replace` критично важлива: інакше спецсимволи у `replacement` (наприклад `$&`, `$1` із вмісту шаблону) трактувалися б як backreferences і зламали б вивід.
173
-
174
- 6. Повертаємо `result`.
175
-
176
- Side effects:
177
-
178
- - **Читання** файлів із диска (синхронна перевірка `existsSync` + асинхронне `readFile`).
179
- - **Кидання `Error`** при відсутності target-файлу — це навмисна поведінка («fail loud — user must know»), а не баг.
180
- - Запису на диск або мережевих викликів **не робить**.
181
-
182
- Складність та обмеження:
183
-
184
- - Цикл лінійний за кількістю template-посилань у тексті; для кожного — один `existsSync` і один `readFile`.
185
- - Файли читаються **послідовно** (через `await` у тілі `for...of`), а не паралельно через `Promise.all`. Це осмислений вибір: правил, як правило, мало, а послідовність робить порядок помилок передбачуваним.
186
- - Заміна виконується через простий `result.replace(fullMatch, ...)` — перший збіг `fullMatch` у `result`. Якщо однакове Markdown-посилання трапляється кілька разів — модифікується лише перше входження (фактичний `matchAll` дасть і інші входження, але кожен з них має той самий `fullMatch`, і їх теж замінить — по одному за крок ітерації; для повних дублікатів це працює коректно).
187
-
188
- ## Залежності
189
-
190
- ### Стандартна бібліотека Node.js
191
-
192
- - `node:fs` → `existsSync` — синхронна перевірка наявності файлу перед читанням.
193
- - `node:fs/promises` → `readFile` — асинхронне читання вмісту target-файлу як UTF-8.
194
- - `node:path` → `basename`, `extname`, `join` — робота з шляхами:
195
- - `extname` — у `langFromExt` для визначення мови;
196
- - `basename` — для отримання базового імені файлу, з якого `normalizeTargetName` витягне target-ім'я;
197
- - `join` — для побудови абсолютного шляху від `ruleDir` + `relPath`.
198
-
199
- ### Зовнішні залежності
200
-
201
- Жодних npm-пакетів. Модуль працює лише на Node.js стандарті.
202
-
203
- ### Споживачі модуля
204
-
205
- Файл лежить у `npm/scripts/lib/` поряд із іншими допоміжними утилітами для збірки правил, тому очікувані споживачі — build-скрипти у `npm/scripts/`, які генерують підсумкові `.mdc`-документи для cursor-rules / Claude-rules. Експортована функція `inlineTemplateLinks` викликається на проміжній стадії пайплайна обробки тексту `.mdc`-файлу разом із `ruleDir`, обчисленим від шляху до самого `.mdc`.
206
-
207
- ## Потік виконання / Використання
208
-
209
- Типовий сценарій інтеграції в build-скрипт:
210
-
211
- ```js
212
- import { readFile, writeFile } from 'node:fs/promises'
213
- import { dirname } from 'node:path'
214
-
215
- import { inlineTemplateLinks } from './lib/inline-template-links.mjs'
216
-
217
- const mdcPath = '/abs/path/to/npm/rules/security/n-security.mdc'
218
- const original = await readFile(mdcPath, 'utf8')
219
-
220
- const ruleDir = dirname(mdcPath) // важливо: каталог, де лежить .mdc
221
- const transformed = await inlineTemplateLinks(original, ruleDir)
222
-
223
- await writeFile(mdcPath, transformed, 'utf8')
224
- ```
225
-
226
- Що відбувається крок-за-кроком на прикладі.
227
-
228
- Вхідний `.mdc`-фрагмент (`ruleDir = .../npm/rules/security/`):
229
-
230
- ```text
231
- Snippet вимоги до `package.json` — див. [тут](./templates/package.json.snippet.json).
232
- ```
233
-
234
- Файл `.../npm/rules/security/templates/package.json.snippet.json`:
235
-
236
- ```json
237
- {
238
- "scripts": {
239
- "lint": "eslint ."
240
- }
241
- }
242
- ```
243
-
244
- Що зробить `inlineTemplateLinks`:
245
-
246
- 1. `matchAll(MD_LINK_RE)` знайде один збіг із href `./templates/package.json.snippet.json`.
247
- 2. `TEMPLATE_SEGMENT_RE` пропустить його (бо є `/templates/`).
248
- 3. `relPath = 'templates/package.json.snippet.json'`, `absPath = '.../npm/rules/security/templates/package.json.snippet.json'`.
249
- 4. `existsSync(absPath)` → `true`, файл читається.
250
- 5. `langFromExt(absPath)` → `'json'`.
251
- 6. `normalizeTargetName('package.json.snippet.json')` → `'package.json'` (спрацює regexp `/^(.+)\.snippet\.[^.]+$/`).
252
- 7. `replacement` буде:
253
-
254
- ````text
255
- `package.json`:
256
-
257
- ```json
258
- {
259
- "scripts": {
260
- "lint": "eslint ."
261
- }
262
- }
263
- ````
264
-
265
- ```
266
-
267
- ```
268
-
269
- 8. Результат заміняє оригінальний Markdown-лінк у тексті.
270
-
271
- Випадки помилок:
272
-
273
- - Якщо `href` веде на неіснуючий файл — кидається `Error` із повним абсолютним шляхом у повідомленні; build-скрипт має право або впасти, або зловити цю помилку.
274
- - Якщо у `text` немає Markdown-посилань або жодне з них не містить `/template(s)/` — функція повертає `text` без модифікацій.
275
- - Якщо template-файл має нерозпізнаване розширення (наприклад `.txt` або `.conf`) — `langFromExt` поверне порожній рядок, і fenced-блок буде без мовного тегу (Markdown це допускає).
276
- - Якщо ім'я template-файлу **не** має одного з суфіксів `.snippet.<ext>` / `.deny.<ext>` / `.contains.<ext>` — `normalizeTargetName` поверне його як є; це нормальна поведінка для «звичайних» template-файлів, у яких саме ім'я і є target-ім'ям.
277
-
278
- ## Rebuild Test
279
-
280
- Якщо видалити цей файл і відтворити його з нуля, мінімально достатній рецепт такий:
281
-
282
- 1. Створи модуль `inline-template-links.mjs` у `npm/scripts/lib/`.
283
- 2. Імпортуй з `node:fs` функцію `existsSync`, з `node:fs/promises` — `readFile`, з `node:path` — `basename`, `extname`, `join`.
284
- 3. Оголоси константи:
285
- - `MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g` — глобальний regexp для Markdown-посилань `[label](./path)`.
286
- - `TEMPLATE_SEGMENT_RE = /\/templates?\//` — фільтр шляхів, що містять `/template/` чи `/templates/`.
287
- - `SLOT_SUFFIX_RES` — масив із трьох **статичних** regexp: `/^(.+)\.snippet\.[^.]+$/`, `/^(.+)\.deny\.[^.]+$/`, `/^(.+)\.contains\.[^.]+$/`. Принципово: жодного `new RegExp(variable)` — захист від ReDoS.
288
- 4. Реалізуй `langFromExt(filePath)`:
289
- - `extname(filePath)` → за вмістом повернути `'json' | 'toml' | 'yaml' | ''` (для `.yml` теж `'yaml'`).
290
- 5. Реалізуй `normalizeTargetName(fileBasename)`:
291
- - Пройди `SLOT_SUFFIX_RES` у заданому порядку; при першому збігу поверни `match[1]`. Інакше — оригінал.
292
- 6. Експортуй `async function inlineTemplateLinks(text, ruleDir)`:
293
- - `matchAll(MD_LINK_RE)` → відфільтруй за `TEMPLATE_SEGMENT_RE.test(href)`.
294
- - Якщо нічого не залишилося — поверни `text`.
295
- - Для кожного збігу: `relPath = href.slice(2)`, `absPath = join(ruleDir, relPath)`; якщо `!existsSync(absPath)` — `throw new Error('inlineTemplateLinks: file not found: <absPath> (referenced from .mdc)')`.
296
- - Читай файл `utf8`, роби `.trim()`, обчисли `lang` і `targetName`, побудуй `replacement = \`\\\`${targetName}\\\`:\\n\\n\\\`\\\`\\\`${lang}\\n${contents}\\n\\\`\\\`\\\``.
297
- - Заміни через `result = result.replace(fullMatch, () => replacement)` (саме callback-форма — щоб уникнути інтерпретації `$&`/`$1` у вмісті template-файлу).
298
- 7. Поверни `result`.
299
-
300
- Контракт, який має зберегтися:
301
-
302
- - Чиста функція над текстом + читання файлів (без записів і без мережі).
303
- - **Fail-loud** на відсутній target.
304
- - Підтримка трьох слот-суфіксів: `snippet`, `deny`, `contains`.
305
- - Підтримка мов підсвічування: `json`, `toml`, `yaml`, інакше — без таргу.
306
- - Жодного `RegExp(variable)`.
307
- - Префікс href повинен починатися з `./`, інакше посилання ігнорується (це закладено в `MD_LINK_RE`).
27
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -3,31 +3,31 @@ type: JS Module
3
3
  title: mirror-parity.mjs
4
4
  resource: npm/scripts/lib/mirror-parity.mjs
5
5
  docgen:
6
- crc: d2140a00
6
+ crc: 093ff2bb
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 90
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль забезпечує паралельність (parity) дзеркал правил. Він порівнює вміст дзеркала `.cursor/rules/n-<id>.mdc` з канонічним вмістом `npm/rules/<id>/<id>.mdc`, який містить вбудовані шаблони (inlined-шаблони). Це гарантує, що локальні копії правил відповідають централізованим, трансформованим версіям, виявляючи дрейф, що виникає при зміні канонічного `.mdc` без регенерації дзеркала.
13
+ Модуль забезпечує паритет дзеркал правил, порівнюючи вміст `.cursor/rules/n-<id>.mdc` з канонічним вмістом `npm/rules/<id>/<id>.mdc`. Він визначає, які дзеркала відстежувати, формує очікуваний вміст цих дзеркал після застосування шаблонів (трансформу, що застосовує `readBundledRuleContent` $\rightarrow$ `inlineTemplateLinks`), та виявляє дрейф, що виникає, коли канонічний `.mdc` змінюється без регенерації дзеркала.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  listManagedMirrors
18
- Визначає список керованих дзеркал правил, які мають канонічне джерело у `npm/rules`.
18
+ Визначає список керованих дзеркал правил, що мають канонічне джерело у `npm/rules`.
19
19
 
20
20
  expectedMirrorContent
21
- Формує очікуваний вміст дзеркала, застосовуючи трансформацію до канонічного вмісту.
21
+ Формує очікуваний вміст дзеркала, застосовуючи трансформації до канонічного файлу.
22
22
 
23
23
  findMirrorDrift
24
- Виявляє і повертає список ідентифікаторів дзеркал, вміст яких відрізняється від очікуваного канонічного.
24
+ Виявляє ідентифікатори дзеркал, чий фактичний вміст відрізняється від очікуваного.
25
25
 
26
26
  ## Публічний API
27
27
 
28
- listManagedMirrors — перераховує дзеркала, які мають визначене основне джерело, і ігнорує зовнішні.
29
- expectedMirrorContent — визначає, яким має бути вміст дзеркала, використовуючи основне джерело з вбудованими шаблонами.
30
- findMirrorDrift — виявляє ідентифікатори дзеркал, чий фактичний вміст відрізняється від очікуваного.
28
+ listManagedMirrors — перераховує керовані дзеркала, які мають визначене основне джерело.
29
+ expectedMirrorContent — визначає бажаний вміст дзеркала, використовуючи шаблонне заміщення даних.
30
+ findMirrorDrift — знаходить ідентифікатори дзеркал, чий фактичний вміст відрізняється від очікуваного.
31
31
 
32
32
  ## Гарантії поведінки
33
33
 
@@ -3,30 +3,30 @@ type: JS Module
3
3
  title: rule-meta.mjs
4
4
  resource: npm/scripts/lib/rule-meta.mjs
5
5
  docgen:
6
- crc: 36aea8cb
6
+ crc: 8eca71d2
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Парсер інтерпретує метадані правил, що знаходяться у `npm/rules/<id>/meta.json`. Він реалізує data-driven логіку для визначення умов активації кожного детектора. Специфікації активації (`auto`) нормалізуються на основі вмісту `meta.json`, підтримуючи чотири типи: константа `RULE_ALWAYS` ("завжди"), залежності від інших правил, шаблони за допомогою `glob`, або незводимі предикати. Парсер не виконує операцій запису у файлову систему чи бази даних і забезпечує безпечну роботу, перехоплюючи помилки.
13
+ Файл зчитує метадані правил з `npm/rules/<id>/main.json`. Він нормалізує специфікації активації (`auto`) та області застосування (`lint`). Специфікація `main.json.auto` може мати чотири форми: константу `RULE_ALWAYS` ("завжди"), масив залежних правил, об'єкт з шаблоном `glob` або об'єкт з предикатом. Це забезпечує механізм для визначення умов активації правила на основі конфігурації `main.json`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- RULE_ALWAYS — константа, що позначає безумовну активацію правила.
18
- parseRuleAutoSpec — Нормалізує значення поля `auto` з метаданих правила у дискриміновану специфікацію активації.
19
- parseRuleLintSpec — Нормалізує значення поля `lint` з метаданих правила у специфікацію області застосування детектора.
20
- readRuleMetaRaw — Читає та парсить вміст `main.json` з каталогу правила, повертаючи його як об'єкт або `null` у разі помилки чи відсутності файлу.
17
+ RULE_ALWAYS — Константа, що позначає безумовну активацію правила.
18
+ parseRuleAutoSpec — Нормалізує значення поля `auto` з `main.json` у дискриміновану специфікацію активації правила.
19
+ parseRuleLintSpec — Нормалізує значення поля `lint` з `main.json` у специфікацію області застосування детектора.
20
+ readRuleMetaRaw — Зчитує та парсить вміст `main.json` з каталогу правила, повертаючи його як об'єкт або `null` у разі помилки чи відсутності файлу.
21
21
 
22
22
  ## Публічний API
23
23
 
24
- RULE_ALWAYS — константа, що вказує на безумовну активацію.
25
- parseRuleAutoSpec — перетворює налаштування автоматичного визначення правил з `meta.json` у структурований формат.
26
- parseRuleLintSpec — перетворює налаштування лінтування з `meta.json` у визначення області дії детектора.
27
- "per-file" — визначає, що детектор працює з окремими зміненими файлами (порівняння змін з оригіналом).
28
- "full" — вказує на повний, нероздільний аналіз між усіма файлами (активується лише при використанні прапорця `--full` або в CI).
29
- readRuleMetaRaw — зчитує та обробляє метадані одного правила з файлу `meta.json`.
24
+ RULE_ALWAYS — позначка для безумовної активації.
25
+ parseRuleAutoSpec — перетворює налаштування автоматичного визначення правил з `main.json` у структурований формат.
26
+ parseRuleLintSpec — перетворює налаштування лінтера з `main.json` у визначення області дії детектора.
27
+ "per-file" — визначає, що детектор аналізує зміни на рівні окремих файлів (порівнює відмінності з оригіналом).
28
+ "full" — визначає, що детектор аналізує весь проєкт одночасно (активується лише при використанні прапорця `--full` або в CI).
29
+ readRuleMetaRaw — зчитує та розбирає метадані одного правила з `main.json`.
30
30
 
31
31
  ## Гарантії поведінки
32
32
 
@@ -3,28 +3,28 @@ type: JS Module
3
3
  title: skill-meta.mjs
4
4
  resource: npm/scripts/lib/skill-meta.mjs
5
5
  docgen:
6
- crc: c0918db5
6
+ crc: c1757867
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Спільний парсер метаданих скіла, що зчитує дані з `npm/skills/<id>/meta.json`. Він є єдиним джерелом правди для конфігурації скілів, визначаючи умови автоактивації (через поле `auto`, де `SKILL_ALWAYS` означає "завжди"), чи виконувати скіл в окремому git-worktree (`worktree`), та чи вимагає скіл запуску з кореня репозиторію (`requireRoot`). Модуль надає механізми для парсингу цих метаданих, перевірки вимог до кореня та читання сирих даних. Він працює в режимі fail-safe, перехоплюючи помилки та повертаючи порожнє значення замість винятків.
13
+ Спільний парсер метаданих скіла, що зчитує інформацію з `main.json`. Цей файл є єдиним джерелом правди для скіла. Модуль дозволяє визначати умови автоактивації скіла (через поле `auto`), вказувати, чи виконувати скіл в окремому git-worktree (`worktree`), та чи вимагає він запуску з кореня репозиторію (`requireRoot`). Надає константу `SKILL_ALWAYS` для безумовної активації. Хелпер використовується іншими компонентами для уникнення дублювання парсингу. Функції забезпечують перехоплення помилок (fail-safe), повертаючи порожнє значення замість винятків.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  SKILL_ALWAYS — надає літерал для безумовної автоактивації скіла.
18
- parseSkillAutoSpec — перетворює значення поля `auto` з метаданих скіла у специфікацію автоактивації.
19
- skillRequiresRoot — визначає, чи вимагає скіл запуску з кореня репозиторію, виходячи з метаданих.
20
- readSkillMetaRaw — читає та парсить файл `main.json` у каталозі скіла, повертаючи його вміст або `null` у разі помилки.
18
+ parseSkillAutoSpec — перетворює значення поля `auto` з `main.json` у специфікацію автоактивації.
19
+ skillRequiresRoot — визначає, чи вимагає скіл запуску з кореня репозиторію, базуючись на метаданих.
20
+ readSkillMetaRaw — читає та парсить файл `main.json` для заданого каталогу скіла.
21
21
 
22
22
  ## Публічний API
23
23
 
24
- SKILL_ALWAYS — маркер, що вказує на безумовну активацію
25
- parseSkillAutoSpec — витягує налаштування автоматичного запуску з `meta.json`
26
- skillRequiresRoot — визначає, чи потрібен запуск скіла з кореня репозиторію
27
- readSkillMetaRaw — зчитує та обробляє метадані окремого скіла з `meta.json`
24
+ SKILL_ALWAYS — позначка для безумовної активації.
25
+ parseSkillAutoSpec — витягує налаштування автоматичного запуску з `main.json` у структуру `SkillAutoSpec`.
26
+ skillRequiresRoot — визначає, чи потрібен запуск скіла з кореня репозиторію (активує захист від запуску з інших місць).
27
+ readSkillMetaRaw — зчитує та розбирає метадані одного скіла з `main.json`.
28
28
 
29
29
  ## Гарантії поведінки
30
30
 
@@ -5,22 +5,22 @@ resource: npm/scripts/lib/timing-summary.mjs
5
5
  docgen:
6
6
  crc: 47660e16
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 95
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Формує рядок, що відображає тривалість у форматі `<ціла>.<десята>s`, забезпечуючи стабільну одиницю вимірювання для всіх інтервалів. Генерує звіт про час виконання для orchestrator `fix` / `lint`, який включає деталі кожного вимірювання та загальний час. Звіт містить маркер `❌` на рядку, якщо відповідний вимірник не пройшов успішно. Дані для звітів отримуються з `package.json` та використовуються у точках виклику `runFixCommand` у `bin/n-cursor.js` та `runLintCli` у `scripts/lib/run-lint-cli.mjs`. Функція є чистою, не виконує I/O, і повертає готовий рядок з фінальним `\n`, друк якого здійснюється на стороні виклику.
13
+ Форматує тривалість у форматі `<ціла>.<десята>s`. Генерує таблицю-резюме часу виконання для оркестратора `fix` або `lint`, яка включає детальні записи та загальний час прогону. Таблиця містить маркер `❌` для позначення невдач. Функція повертає готовий рядок із фінальним `\n`; друк здійснюється на стороні виклику.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- formatDurationMs форматує тривалість у мілісекундах як рядок у форматі `<ціла>.<десята>s`.
18
- formatTimingSummary генерує багаторядковий текстовий звіт про час виконання, включаючи деталі кожного запису та загальний час.
17
+ formatDurationMs форматує тривалість у мілісекундах у рядок у форматі `<ціла>.<десята>s`.
18
+ formatTimingSummary генерує багаторядковий рядок із таблицею-резюме часу виконання, включаючи окремі записи та загальний час.
19
19
 
20
20
  ## Публічний API
21
21
 
22
- ⏱ formatDurationMs: Перетворює мілісекунди у формат `<sec>.<десята>s`, використовуючи округлення вниз.
23
- ⏱ formatTimingSummary: Генерує багаторядковий вивід таблиці-резюме часу виконання.
22
+ ⏱ formatDurationMs: Перетворює мілісекунди у формат `<sec>.<десята>s`, використовуючи нижнє округлення для забезпечення консистентності виводу.
23
+ ⏱ formatTimingSummary: Генерує багаторядковий текстовий звіт про час виконання, структурований як таблиця з сумарними даними.
24
24
 
25
25
  ## Гарантії поведінки
26
26
 
@@ -3,24 +3,26 @@ type: JS Module
3
3
  title: worktree-notice.mjs
4
4
  resource: npm/scripts/lib/worktree-notice.mjs
5
5
  docgen:
6
- crc: 1f7d5e0d
6
+ crc: b4358793
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Цей файл вшиває worktree-інструкцію у синкнутий `SKILL.md` (рішення D2 зі spec). Коли `meta.json.worktree === true`, скіл вставляє/замінює ідемпотентний ре-синкнутий блок, що містить маркери WORKTREE_START та WORKTREE_END, забезпечуючи виконання скілу в окремому git-worktree та запобігаючи паралелізації. Функція `injectWorktreeNotice` керує наявністю або відсутністю цього блоку в `SKILL.md` на основі конфігурації.
11
+ ## Огляд
12
+
13
+ Управляє вставкою інструкцій для виконання команд у ізольованому git-worktree у синкнутий `SKILL.md` (рішення D2 зі spec). Коли `main.json.worktree === true`, інструкції вставляються між маркерами `WORKTREE_START` та `WORKTREE_END`. Це забезпечує ре-синк ідемпотентність: наявний блок замінюється, а при `main.json.worktree === false` — видаляється. Механізм адаптований для агента, який читає `SKILL.md`.
12
14
 
13
15
  ## Поведінка
14
16
 
15
- WORKTREE_START Маркер початку блоку інструкцій для роботи в окремому git-worktree.
16
- WORKTREE_END Маркер кінця блоку інструкцій для роботи в окремому git-worktree.
17
- injectWorktreeNotice Вставляє, оновлює або видаляє блок інструкцій для роботи в worktree у вмісті SKILL.md залежно від булевого значення.
17
+ WORKTREE_START: Позначає початок блоку інструкцій для роботи в окремому git-worktree.
18
+ WORKTREE_END: Позначає кінець блоку інструкцій для роботи в окремому git-worktree.
19
+ injectWorktreeNotice: Вставляє, оновлює або видаляє блок інструкцій для роботи в worktree у вмісті SKILL.md залежно від булевого значення.
18
20
 
19
21
  ## Публічний API
20
22
 
21
- WORKTREE_START — Позначає початок секції, що описує робоче дерево.
22
- WORKTREE_END — Позначає кінець секції, що описує робоче дерево.
23
- injectWorktreeNotice — Вставляє, змінює або видаляє блок інформації про робоче дерево у файлі `SKILL.md`.
23
+ WORKTREE_START — Позначає початок блоку, що описує робоче дерево.
24
+ WORKTREE_END — Позначає кінець блоку, що описує робоче дерево.
25
+ injectWorktreeNotice — Додає, змінює або видаляє блок робочого дерева у файлі `SKILL.md`.
24
26
 
25
27
  ## Гарантії поведінки
26
28
 
@@ -4,6 +4,7 @@ import { basename, extname, join } from 'node:path'
4
4
 
5
5
  const MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
6
6
  const TEMPLATE_SEGMENT_RE = /\/templates?\//
7
+ const MDC_EXT_RE = /\.mdc$/
7
8
  /** Статичні regexp-літерали `^(.+)\.<slot>\.<ext>$` — без `RegExp(variable)`. */
8
9
  const SLOT_SUFFIX_RES = [/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
9
10
 
@@ -66,3 +67,33 @@ export async function inlineTemplateLinks(text, ruleDir) {
66
67
 
67
68
  return result
68
69
  }
70
+
71
+ /**
72
+ * Finds markdown links whose href ends with `.mdc` (and is not a /template/ path) and
73
+ * replaces them with the raw markdown content of the linked file (no fencing).
74
+ * Intended for per-concern section files living alongside their .mjs implementations.
75
+ * Throws Error if a matched link target doesn't exist (fail loud).
76
+ * @param {string} text .mdc file contents (after inlineTemplateLinks)
77
+ * @param {string} ruleDir absolute path to the rule directory
78
+ * @returns {Promise<string>} transformed text
79
+ */
80
+ export async function inlineMarkdownIncludes(text, ruleDir) {
81
+ const matches = [...text.matchAll(MD_LINK_RE)].filter(m => MDC_EXT_RE.test(m[2]) && !TEMPLATE_SEGMENT_RE.test(m[2]))
82
+ if (matches.length === 0) return text
83
+
84
+ let result = text
85
+ for (const match of matches) {
86
+ const [fullMatch, , href] = match
87
+ const relPath = href.slice(2) // strip leading ./
88
+ const absPath = join(ruleDir, relPath)
89
+
90
+ if (!existsSync(absPath)) {
91
+ throw new Error(`inlineMarkdownIncludes: file not found: ${absPath} (referenced from .mdc)`)
92
+ }
93
+
94
+ const raw = await readFile(absPath, 'utf8')
95
+ result = result.replace(fullMatch, () => raw.trim())
96
+ }
97
+
98
+ return result
99
+ }
@@ -9,7 +9,7 @@
9
9
  import { existsSync, readdirSync, readFileSync } from 'node:fs'
10
10
  import { dirname, join } from 'node:path'
11
11
 
12
- import { inlineTemplateLinks } from './inline-template-links.mjs'
12
+ import { inlineMarkdownIncludes, inlineTemplateLinks } from './inline-template-links.mjs'
13
13
 
14
14
  const MIRROR_PREFIX = 'n-'
15
15
  const MDC_EXT = '.mdc'
@@ -41,8 +41,10 @@ export function listManagedMirrors(repoRoot) {
41
41
  * @param {string} canonicalPath абсолютний шлях `npm/rules/<id>/<id>.mdc`
42
42
  * @returns {Promise<string>} очікуваний текст дзеркала
43
43
  */
44
- export function expectedMirrorContent(canonicalPath) {
45
- return inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dirname(canonicalPath))
44
+ export async function expectedMirrorContent(canonicalPath) {
45
+ const dir = dirname(canonicalPath)
46
+ const withTemplates = await inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dir)
47
+ return inlineMarkdownIncludes(withTemplates, dir)
46
48
  }
47
49
 
48
50
  /**