@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.
- package/CHANGELOG.md +12 -0
- package/bin/n-cursor.js +8 -7
- package/package.json +1 -1
- package/rules/abie/js/env_dns.mdc +33 -0
- package/rules/abie/js/firebase_hosting.mdc +3 -0
- package/rules/abie/js/hc_pairing.mdc +23 -0
- package/rules/abie/js/http_route_base.mdc +25 -0
- package/rules/abie/js/ua_http_route.mdc +47 -0
- package/rules/abie/js/ua_node_selector.mdc +27 -0
- package/rules/abie/main.mdc +11 -127
- package/rules/doc-files/js/docs/index.md +15 -15
- package/rules/js/docs/main.md +6 -6
- package/rules/js/js/docs/check.md +11 -10
- package/rules/js/js/docs/tooling.md +7 -7
- package/rules/js/js/docs/utils_imports.md +13 -12
- package/rules/test/js/docs/stryker_config.md +12 -12
- package/rules/test/js/docs/vitest-config-pool-forks.md +13 -7
- package/scripts/auto-rules.mjs +6 -6
- package/scripts/auto-skills.mjs +3 -3
- package/scripts/docs/auto-rules.md +17 -31
- package/scripts/docs/auto-skills.md +18 -163
- package/scripts/docs/sync-setup-bun-deps-action.md +6 -5
- package/scripts/lib/docs/inline-template-links.md +13 -293
- package/scripts/lib/docs/mirror-parity.md +9 -9
- package/scripts/lib/docs/rule-meta.md +12 -12
- package/scripts/lib/docs/skill-meta.md +9 -9
- package/scripts/lib/docs/timing-summary.md +6 -6
- package/scripts/lib/docs/worktree-notice.md +10 -8
- package/scripts/lib/inline-template-links.mjs +31 -0
- package/scripts/lib/mirror-parity.mjs +5 -3
- package/scripts/lib/rule-meta.mjs +6 -6
- package/scripts/lib/skill-meta.mjs +6 -6
- package/scripts/lib/worktree-notice.mjs +2 -2
- package/scripts/utils/docs/resolve-js-root.md +4 -4
- 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:
|
|
6
|
+
crc: b659349c
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
7
9
|
---
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Огляд
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Цей модуль реалізує механізми вбудовування контенту в текстові документи, використовуючи конфігурації з `package.json.snippet.json` та `package.json`. Він замінює посилання в тексті на вміст файлів-шаблонів, розташованих у каталозі правил, а також на вміст файлів з розширенням .mdc, які не є шаблонами. Функції дозволяють вставляти посилання на шаблони (`inlineTemplateLinks`) та включати вміст Markdown (`inlineMarkdownIncludes`).
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Поведінка
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
+
## Публічний API
|
|
21
21
|
|
|
22
|
-
|
|
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
|
-
|
|
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:
|
|
6
|
+
crc: 093ff2bb
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score:
|
|
8
|
+
score: 90
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Огляд
|
|
12
12
|
|
|
13
|
-
Модуль забезпечує
|
|
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
|
-
Визначає список керованих дзеркал правил,
|
|
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:
|
|
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
|
-
|
|
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 —
|
|
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 — перетворює налаштування автоматичного визначення правил з `
|
|
26
|
-
parseRuleLintSpec — перетворює налаштування
|
|
27
|
-
"per-file" — визначає, що детектор
|
|
28
|
-
"full" —
|
|
29
|
-
readRuleMetaRaw — зчитує та
|
|
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:
|
|
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
|
-
Спільний парсер метаданих скіла, що зчитує
|
|
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`
|
|
18
|
+
parseSkillAutoSpec — перетворює значення поля `auto` з `main.json` у специфікацію автоактивації.
|
|
19
|
+
skillRequiresRoot — визначає, чи вимагає скіл запуску з кореня репозиторію, базуючись на метаданих.
|
|
20
|
+
readSkillMetaRaw — читає та парсить файл `main.json` для заданого каталогу скіла.
|
|
21
21
|
|
|
22
22
|
## Публічний API
|
|
23
23
|
|
|
24
|
-
SKILL_ALWAYS —
|
|
25
|
-
parseSkillAutoSpec — витягує налаштування автоматичного запуску з `
|
|
26
|
-
skillRequiresRoot — визначає, чи потрібен запуск скіла з кореня репозиторію
|
|
27
|
-
readSkillMetaRaw — зчитує та
|
|
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:
|
|
8
|
+
score: 95
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Огляд
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Форматує тривалість у форматі `<ціла>.<десята>s`. Генерує таблицю-резюме часу виконання для оркестратора `fix` або `lint`, яка включає детальні записи та загальний час прогону. Таблиця містить маркер `❌` для позначення невдач. Функція повертає готовий рядок із фінальним `\n`; друк здійснюється на стороні виклику.
|
|
14
14
|
|
|
15
15
|
## Поведінка
|
|
16
16
|
|
|
17
|
-
formatDurationMs форматує тривалість у мілісекундах
|
|
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:
|
|
6
|
+
crc: b4358793
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
|
|
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
|
|
16
|
-
WORKTREE_END
|
|
17
|
-
injectWorktreeNotice
|
|
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 —
|
|
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
|
-
|
|
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
|
/**
|