@nitra/cursor 1.8.219 → 1.8.221

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,18 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.221] - 2026-05-10
8
+
9
+ ### Changed
10
+
11
+ - **ci4.mdc:** наповнено правило людинозрозумілим описом C4-моделі як джерела істини. Markdown-файли (C4 + ADR + тести + документація) — офіційне джерело істини про проєкт. Перед змінами агент аналізує відповідні C4-файли; кожна зміна, що впливає на модель, супроводжується оновленням C4-схеми у тому ж PR. ADR описує вплив рішення на C4 (які контейнери/компоненти з'являються/зникають/змінюють відповідальність). Кожен C4-компонент має посилання на відповідні тести. C4-схеми — частина користувацької документації, не закритий артефакт. Алгоритмічної `check-ci4.mjs` поки немає — правило процесне; `## Перевірка` залишено для майбутньої формалізації.
12
+
13
+ ## [1.8.220] - 2026-05-09
14
+
15
+ ### Fixed
16
+
17
+ - **k8s / `prodOverlayHpaPdbOverrideNeeds`:** виключено Kustomize Component (`kind: Component`) з prod-overlay-перевірки. Раніше `<pkg>/k8s/components/kustomization.yaml` помилково тригерив `прод-оверлей має перевизначати spec.minReplicas/maxReplicas/minAvailable` — але Component є **джерелом** ресурсів для overlays, не overlay сам по собі. Прод-перезаписи живуть у `ru/` / `ua/` / `prod/` тощо, що підключають Component через `components:`. Додано ранній return за `kind: Component` у `npm/scripts/check-k8s.mjs`; уточнення додано до `npm/mdc/k8s.mdc` і регресійний тест у `npm/tests/check-k8s-schema.test.mjs`.
18
+
7
19
  ## [1.8.219] - 2026-05-09
8
20
 
9
21
  ### Added
package/bin/n-cursor.js CHANGED
@@ -1137,7 +1137,8 @@ async function readBundledVersionAt(packageRoot) {
1137
1137
  * бо ES-модулі вже завантажені у V8 (RULE_MIGRATIONS, detectAutoRules тощо) і нова логіка
1138
1138
  * без повної заміни процесу не підхопиться. Захист від нескінченного циклу — env `NITRA_CURSOR_REEXEC=1`.
1139
1139
  * @param {string} effectivePackageRoot шлях, повернутий `upgradeNitraCursorToLatestAndBunInstall`
1140
- * @returns {Promise<void>} повертається лише якщо re-exec не потрібен (інакше викликає `process.exit`)
1140
+ * @returns {Promise<void>} повертається лише якщо re-exec не потрібен; інакше кидає `ReexecHandoff`,
1141
+ * який ловить top-level catch і прокидає exit-код у `process.exitCode`
1141
1142
  */
1142
1143
  async function reexecIfPackageVersionChanged(effectivePackageRoot) {
1143
1144
  if (env.NITRA_CURSOR_REEXEC === '1') {
@@ -1167,7 +1168,23 @@ async function reexecIfPackageVersionChanged(effectivePackageRoot) {
1167
1168
  if (result.error) {
1168
1169
  throw result.error
1169
1170
  }
1170
- process.exit(typeof result.status === 'number' ? result.status : 1)
1171
+ throw new ReexecHandoff(typeof result.status === 'number' ? result.status : 1)
1172
+ }
1173
+
1174
+ /**
1175
+ * Сентинельна помилка, яку кидає `reexecIfPackageVersionChanged` після успішного re-exec.
1176
+ * Top-level catch розпізнає її й виставляє `process.exitCode = code` без stack-trace —
1177
+ * процес тоді коректно завершується з тим самим кодом, що й child re-exec-у.
1178
+ */
1179
+ class ReexecHandoff extends Error {
1180
+ /**
1181
+ * @param {number} code exit-код, який повернув child-процес
1182
+ */
1183
+ constructor(code) {
1184
+ super('reexec-handoff')
1185
+ this.name = 'ReexecHandoff'
1186
+ this.code = code
1187
+ }
1171
1188
  }
1172
1189
 
1173
1190
  /**
@@ -1329,6 +1346,10 @@ try {
1329
1346
  process.exitCode = 1
1330
1347
  }
1331
1348
  }
1332
- } catch {
1333
- process.exitCode = 1
1349
+ } catch (error) {
1350
+ if (error instanceof ReexecHandoff) {
1351
+ process.exitCode = error.code
1352
+ } else {
1353
+ process.exitCode = 1
1354
+ }
1334
1355
  }
package/mdc/ci4.mdc ADDED
@@ -0,0 +1,51 @@
1
+ ---
2
+ description: Дизайн проєкту за C4 model (https://c4model.com); Markdown — джерело істини про архітектуру, рішення, тести й документацію
3
+ alwaysApply: true
4
+ version: '1.0'
5
+ ---
6
+
7
+ C4-діаграми проєкту живуть у Markdown поряд із кодом. Це не довідник «для людей із порталу
8
+ архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед
9
+ будь-якою зміною коду. Тому правила нижче — не оформлення, а робочий процес.
10
+
11
+ ## Markdown як джерело істини
12
+
13
+ Уся ключова інформація про проєкт — архітектура (C4), рішення (ADR), тести й документація —
14
+ зберігається у `.md`/`.mdc`-файлах і є **офіційним джерелом істини**. Це єдиний спосіб
15
+ тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
16
+ важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
17
+
18
+ ## Аналіз перед зміною
19
+
20
+ Перш ніж вносити зміни, агент **читає відповідні C4-файли**: контекст, контейнери, компоненти
21
+ тієї ділянки, до якої входить редагований код. Без цього кроку легко зламати приховані
22
+ залежності між контейнерами або «загубити» зовнішню інтеграцію.
23
+
24
+ ## Оновлення синхронно зі змінами
25
+
26
+ Кожна зміна, що впливає на C4-модель — нова інтеграція, новий компонент, перейменування,
27
+ зміна напрямку залежності, видалення сервісу — **супроводжується оновленням C4-схеми у тому ж
28
+ PR**. C4 не оновлюється «потім» окремою задачею: розсинхрон між кодом і моделлю — найчастіша
29
+ причина, чому архітектурні документи перестають читати.
30
+
31
+ ## Зв'язок із ADR
32
+
33
+ ADR (`docs/adr/`) описує **вплив рішення на C4**: які контейнери/компоненти з'являються,
34
+ зникають, змінюють відповідальність. Якщо рішення суттєве — у тілі ADR явно пишемо, які
35
+ C4-схеми потрібно оновити, і робимо це у тому ж PR.
36
+
37
+ ## Зв'язок із тестами
38
+
39
+ Кожен C4-компонент має **посилання на відповідні тести** — інтеграційні, e2e або юніт залежно
40
+ від рівня. Так перехід «компонент → як його перевіряємо» займає один клік, а не пошук по
41
+ репозиторію.
42
+
43
+ ## Зв'язок із документацією
44
+
45
+ C4-схеми — частина **користувацької документації**, а не закритий артефакт для команди.
46
+ Контекстна діаграма (рівень 1) і контейнерна (рівень 2) живуть там, де читач шукає вступ у
47
+ проєкт, а не у відокремленій теці «for-architects».
48
+
49
+ ## Перевірка
50
+
51
+ `npx @nitra/cursor check ci4`
package/mdc/k8s.mdc CHANGED
@@ -387,6 +387,8 @@ images:
387
387
 
388
388
  **Overlays** (`ua/`, `ru/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно.
389
389
 
390
+ **`<pkg>/k8s/components/kustomization.yaml`** має `kind: Component` (не `kind: Kustomization`) — це **джерело** канонічних HPA/PDB для всіх overlays, а не overlay сам по собі. Прод-перезаписи (`/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`) живуть у `<env>/kustomization.yaml`, що підключає Component через `components:`. У самому Component patches не потрібні — він env-неутральний; **`check k8s`** не вимагає прод-патчів від `components/kustomization.yaml`.
391
+
390
392
  У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремий **`hpa.yaml`** / **`pdb.yaml`**, якщо такі потрібні для цього середовища. **`check k8s`** звіряє прив'язку за іменами:
391
393
 
392
394
  - **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.219",
3
+ "version": "1.8.221",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -12,7 +12,14 @@
12
12
  */
13
13
 
14
14
  /** Порядок автододавання skills відповідно до `auto-skills.md`. */
15
- export const AUTO_SKILL_ORDER = Object.freeze(['abie-kustomize', 'fix', 'lint', 'llm-patch', 'publish-telegram', 'taze'])
15
+ export const AUTO_SKILL_ORDER = Object.freeze([
16
+ 'abie-kustomize',
17
+ 'fix',
18
+ 'lint',
19
+ 'llm-patch',
20
+ 'publish-telegram',
21
+ 'taze'
22
+ ])
16
23
 
17
24
  /**
18
25
  * Залежність скілів від правил (`auto-skills.md` синтаксис `skill - [rules]`).
@@ -104,10 +104,10 @@ export async function check() {
104
104
  fail('Відсутній bun.lock — запусти bun i')
105
105
  }
106
106
 
107
- if (!existsSync('bunfig.toml')) {
108
- fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
109
- } else {
107
+ if (existsSync('bunfig.toml')) {
110
108
  pass('bunfig.toml є (структуру перевіряє bun run lint-conftest → bun.bunfig)')
109
+ } else {
110
+ fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
111
111
  }
112
112
 
113
113
  const cursorRules = await loadNCursorRules()
@@ -160,9 +160,8 @@ async function readBaseVersion(baseRef, ws) {
160
160
  * @returns {boolean} `true`, якщо запис для `version` знайдено
161
161
  */
162
162
  function changelogHasVersionEntry(text, version) {
163
- const escaped = version.replaceAll(/[.+*?^$()[\]{}|\\]/g, String.raw`\$&`)
164
- const re = new RegExp(String.raw`^##\s+\[${escaped}\]`, 'm')
165
- return re.test(text)
163
+ const needle = `## [${version}]`
164
+ return text.startsWith(needle) || text.includes(`\n${needle}`)
166
165
  }
167
166
 
168
167
  /**
@@ -3,9 +3,9 @@
3
3
  * ув'язування `.avif`-двійників з посиланнями у `.vue`/`.html`.
4
4
  *
5
5
  * Дії під час `check image-avif`:
6
- * 1. `npx @nitra/minify-image --src=. --write --avif` — генерує AVIF-двійники.
6
+ * 1. `npx \@nitra/minify-image --src=. --write --avif` — генерує AVIF-двійники.
7
7
  * 2. У кожному workspace-пакеті переписує raster-посилання у `.vue`/`.html` на `.avif`
8
- * (де AVIF-двійник реально існує на диску). Pakety з `"@nitra/minify-image": {
8
+ * (де AVIF-двійник реально існує на диску). Pakety з `"\@nitra/minify-image": {
9
9
  * "disable-avif": true }` у `package.json` пропускаються.
10
10
  * 3. Прибирає AVIF-сироти — `<name>.<ext>.avif`, на які не лишилось жодного посилання
11
11
  * у `.vue`/`.html` репозиторію, видаляються (умова правила: «AVIF лишається лише
@@ -27,13 +27,14 @@ import { env } from 'node:process'
27
27
 
28
28
  import { createCheckReporter } from './utils/check-reporter.mjs'
29
29
  import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
30
+ import { resolveCmd } from './utils/resolve-cmd.mjs'
30
31
  import { walkDir } from './utils/walkDir.mjs'
31
32
  import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
32
33
 
33
34
  /** Імʼя CLI-пакета, який генерує AVIF. */
34
35
  const MINIFY_PACKAGE_NAME = '@nitra/minify-image'
35
36
 
36
- /** Поле в `package.json` для конфігу @nitra/minify-image (наприклад, `disable-avif`). */
37
+ /** Поле в `package.json` для конфігу `\@nitra/minify-image` (наприклад, `disable-avif`). */
37
38
  const PKG_CONFIG_FIELD = '@nitra/minify-image'
38
39
 
39
40
  /**
@@ -279,7 +280,7 @@ async function checkVueAvifImports(ignorePaths, usedAvifAbs, stats, pass, fail)
279
280
 
280
281
  /**
281
282
  * Чи є в репозиторії хоч один raster-файл, який мав би сенс конвертувати у AVIF.
282
- * Якщо немає — `npx @nitra/minify-image` нема що робити, тож зайвий запуск пропускаємо
283
+ * Якщо немає — `npx \@nitra/minify-image` нема що робити, тож зайвий запуск пропускаємо
283
284
  * (важливо у тестах: фікстурні `.png`-імпорти посилаються на неіснуючі файли, тож
284
285
  * minify-image все одно нічого не згенерує — а зайвий npx-спавн повільний і робить шум).
285
286
  * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
@@ -299,7 +300,7 @@ async function hasAnyRasterImage(ignorePaths) {
299
300
  }
300
301
 
301
302
  /**
302
- * Запускає `npx @nitra/minify-image --src=. --write --avif` для генерації AVIF-двійників.
303
+ * Запускає `npx \@nitra/minify-image --src=. --write --avif` для генерації AVIF-двійників.
303
304
  *
304
305
  * Виклик best-effort: якщо мережа/кеш недоступні чи бінарника нема — лог-варн без падіння
305
306
  * перевірки (валідації package.json і vue-refs все одно прогоняться, vue-refs на
@@ -309,7 +310,14 @@ async function hasAnyRasterImage(ignorePaths) {
309
310
  */
310
311
  function runAvifGeneration() {
311
312
  if (env.NITRA_CURSOR_NO_AVIF_RUN === '1') return
312
- const result = spawnSync('npx', [MINIFY_PACKAGE_NAME, '--src=.', '--write', '--avif'], {
313
+ const npxPath = resolveCmd('npx')
314
+ if (!npxPath) {
315
+ console.log(
316
+ ` ⚠️ 'npx' не знайдено в PATH — пропускаємо генерацію AVIF; vue/html-перевірка покаже файли, для яких не вистачає .avif`
317
+ )
318
+ return
319
+ }
320
+ const result = spawnSync(npxPath, [MINIFY_PACKAGE_NAME, '--src=.', '--write', '--avif'], {
313
321
  stdio: 'inherit',
314
322
  env
315
323
  })
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * **Що покрила Rego** (`bun run lint-conftest`,
13
13
  * `npm/policy/image_compress/package_json/`):
14
- * - `scripts.lint-image` викликає `npx @nitra/minify-image --src=. --write`
14
+ * - `scripts.lint-image` викликає `npx \@nitra/minify-image --src=. --write`
15
15
  * без `--avif` (AVIF — окреме правило `image-avif`);
16
16
  * - агрегований `lint` (якщо є) містить `bun run lint-image`;
17
17
  * - `@nitra/minify-image` НЕ у `dependencies` / `devDependencies` (через `npx`).
@@ -207,35 +207,56 @@ async function checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJso
207
207
  let violations = 0
208
208
  for (const absPath of sourcePaths) {
209
209
  const rel = relPosix(absPackageRoot, absPath)
210
- if (!isInsideConnDir(rel, connDir)) continue
211
- if (!isConnFileRulesSourceFile(rel)) continue
212
- // пропускаємо реекспортний барель `index.*` (якщо знадобиться) і прихований .d.ts
213
- const base = rel.slice(rel.lastIndexOf('/') + 1)
214
- if (base.startsWith('index.')) continue
215
-
210
+ if (!isConnFileToCheck(rel, connDir)) continue
216
211
  const content = await readFile(absPath, 'utf8')
217
212
  for (const v of findConnFileRuleViolations(content, rel)) {
218
213
  violations++
219
- if (v.kind === 'name') {
220
- fail(
221
- `${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
222
- `'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
223
- `(kebab-case, [a-z0-9-])`
224
- )
225
- } else if (v.kind === 'default-export') {
226
- fail(`${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`)
227
- } else {
228
- const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
229
- fail(
230
- `${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
231
- `(camelCase від назви файла); знайдено: ${found}`
232
- )
233
- }
214
+ fail(formatConnFileViolation(v, label, rel, connDir))
234
215
  }
235
216
  }
236
217
  return violations
237
218
  }
238
219
 
220
+ /**
221
+ * Чи `rel` — це conn-файл, який треба валідувати: під `connDir/`, з JS/TS-розширенням,
222
+ * не `index.*` (який є реекспортним барелем).
223
+ * @param {string} rel відносний шлях у posix-форматі
224
+ * @param {string} connDir каталог conn-файлів (наприклад `src/conn`)
225
+ * @returns {boolean} true, якщо файл потрібно перевірити
226
+ */
227
+ function isConnFileToCheck(rel, connDir) {
228
+ if (!isInsideConnDir(rel, connDir)) return false
229
+ if (!isConnFileRulesSourceFile(rel)) return false
230
+ const base = rel.slice(rel.lastIndexOf('/') + 1)
231
+ return !base.startsWith('index.')
232
+ }
233
+
234
+ /**
235
+ * Будує повідомлення про конкретне порушення canon-у файла з `connDir/`.
236
+ * @param {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }} v опис порушення
237
+ * @param {string} label префікс повідомлення `[<pkg>] `
238
+ * @param {string} rel відносний шлях файла
239
+ * @param {string} connDir каталог conn-файлів
240
+ * @returns {string} повний текст повідомлення для `fail(...)`
241
+ */
242
+ function formatConnFileViolation(v, label, rel, connDir) {
243
+ if (v.kind === 'name') {
244
+ return (
245
+ `${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
246
+ `'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
247
+ `(kebab-case, [a-z0-9-])`
248
+ )
249
+ }
250
+ if (v.kind === 'default-export') {
251
+ return `${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`
252
+ }
253
+ const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
254
+ return (
255
+ `${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
256
+ `(camelCase від назви файла); знайдено: ${found}`
257
+ )
258
+ }
259
+
239
260
  /**
240
261
  * Перевіряє правило «CheckEnv» для пакета.
241
262
  * @param {string} absPackageRoot абсолютний корінь пакета
@@ -297,12 +318,12 @@ async function checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, f
297
318
  async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, passFn) {
298
319
  const label = `[${rootDir}] `
299
320
  const absPackageRoot = join(process.cwd(), rootDir)
300
- const pkgJson = await loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail)
321
+ const pkgJson = await loadPackageJson(rootDir)
301
322
 
302
323
  // Frontend-пакети (vite у devDependencies) виходять за межі js-run:
303
324
  // браузерний бандл не має `node:process`, а `process.env.*` бандлер
304
- // обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо,
305
- // bunyan-залежність уже звірено в `loadPackageJsonAndCheckBunyanDeps`.
325
+ // обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
326
+ // bunyan-залежність валідується в Rego (`bun run lint-conftest`).
306
327
  if (packageJsonHasViteDevDependency(pkgJson)) {
307
328
  passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
308
329
  return
@@ -343,7 +364,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, pass
343
364
  passFn(`${label}немає 'new Promise(r => setTimeout(r, ms))' — паузи через 'node:timers/promises'`)
344
365
  }
345
366
 
346
- await checkOtelConfigmap(rootDir, label, fail, passFn)
367
+ checkOtelConfigmap(rootDir, passFn)
347
368
 
348
369
  checkDepcheckInWorkflows(rootDir, workflows, label, fail, passFn)
349
370
  }
@@ -388,41 +409,31 @@ function packageJsonHasViteDevDependency(pkgJson) {
388
409
  }
389
410
 
390
411
  /**
391
- * Завантажує `package.json` пакета (якщо є) і реєструє порушення для bunyan-залежностей.
412
+ * Завантажує `package.json` пакета (якщо є). Заборону `@nitra/bunyan` / `bunyan`
413
+ * у dependencies/devDependencies перенесено в Rego (`npm/policy/js_run/package_json/`);
414
+ * `bun run lint-conftest` запускає її по всіх workspace `package.json`. Тут лишилася
415
+ * лише AST-перевірка імпортів.
392
416
  * @param {string} rootDir відносний шлях workspace
393
- * @param {string} label префікс повідомлення `[<pkg>] `
394
- * @param {(msg: string) => void} fail callback при помилці
395
417
  * @returns {Promise<unknown>} розпарсений package.json або null
396
418
  */
397
- async function loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail) {
419
+ async function loadPackageJson(rootDir) {
398
420
  const pkgPath = join(rootDir, 'package.json')
399
421
  if (!existsSync(pkgPath)) return null
400
- const pkgJson = JSON.parse(await readFile(pkgPath, 'utf8'))
401
- // Заборону `@nitra/bunyan` / `bunyan` у dependencies/devDependencies перенесено
402
- // в Rego (`npm/policy/js_run/package_json/`); `bun run lint-conftest` запускає
403
- // її по всіх workspace `package.json`. Тут лишилася лише AST-перевірка імпортів.
404
- void label
405
- void fail
406
- return pkgJson
422
+ return JSON.parse(await readFile(pkgPath, 'utf8'))
407
423
  }
408
424
 
409
425
  /**
410
- * Перевіряє вміст `k8s/base/configmap.yaml` пакета на наявність OTEL_RESOURCE_ATTRIBUTES
411
- * з обов'язковими `service.name=` та `service.namespace=` всередині.
426
+ * Перевіряє наявність `k8s/base/configmap.yaml` пакета. Структуру (наявність
427
+ * `OTEL_RESOURCE_ATTRIBUTES` з обов'язковими `service.name=` / `service.namespace=`)
428
+ * перенесено в Rego (`npm/policy/js_run/configmap/`); `bun run lint-conftest`
429
+ * запускає її на всіх `k8s/base/configmap.yaml`.
412
430
  * @param {string} rootDir відносний шлях workspace
413
- * @param {string} label префікс повідомлення `[<pkg>] `
414
- * @param {(msg: string) => void} fail callback при помилці
415
431
  * @param {(msg: string) => void} passFn успішне повідомлення
416
- * @returns {Promise<void>} завершується після перевірки configmap
432
+ * @returns {void}
417
433
  */
418
- function checkOtelConfigmap(rootDir, label, fail, passFn) {
434
+ function checkOtelConfigmap(rootDir, passFn) {
419
435
  const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
420
436
  if (!existsSync(configmapPath)) return
421
- // Перевірку `OTEL_RESOURCE_ATTRIBUTES` має містити `service.name=` /
422
- // `service.namespace=` перенесено в Rego (`npm/policy/js_run/configmap/`);
423
- // `bun run lint-conftest` запускає її на всіх `k8s/base/configmap.yaml`.
424
- void label
425
- void fail
426
437
  passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — bun run lint-conftest → js_run.configmap)`)
427
438
  }
428
439