@nitra/cursor 1.8.222 → 1.8.228

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.
@@ -22,6 +22,7 @@ import { join } from 'node:path'
22
22
  import { createCheckReporter } from './utils/check-reporter.mjs'
23
23
  import { eventPathsIncludeExact, parseWorkflowYaml } from './utils/gha-workflow.mjs'
24
24
  import { resolveCmd } from './utils/resolve-cmd.mjs'
25
+ import { runConftestBatch } from './utils/run-conftest-batch.mjs'
25
26
 
26
27
  /** Шаблони наявності MegaLinter у вмісті workflow */
27
28
  const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/megalinter/i]
@@ -276,7 +277,7 @@ async function checkLintGaScript(passFn, failFn) {
276
277
  * @param {(msg: string) => void} passFn callback при успішній перевірці
277
278
  * @param {(msg: string) => void} failFn callback при помилці
278
279
  */
279
- function checkShellcheckInstalled(passFn, failFn) {
280
+ export function checkShellcheckInstalled(passFn, failFn) {
280
281
  if (resolveCmd('shellcheck')) {
281
282
  passFn('shellcheck встановлений локально, actionlint виконуватиме SC-правила, як у CI')
282
283
  return
@@ -325,7 +326,84 @@ function checkGaWorkflowFiles(wfDir, files, pass, fail) {
325
326
  }
326
327
 
327
328
  /**
328
- * Перевіряє відповідність проєкту правилам ga.mdc
329
+ * Per-workflow Rego-полісі: namespace конкретний workflow-файл. Кожен пакет
330
+ * у `npm/policy/ga/<name>/` містить правила специфічні для ОДНОГО workflow,
331
+ * тому conftest викликаємо з `--namespace` окремо на кожен файл (інакше правила
332
+ * чужого workflow застосуються до неправильного файла).
333
+ * @type {Array<{ workflow: string, namespace: string, policyDirRel: string }>}
334
+ */
335
+ const GA_PER_WORKFLOW_REGO_TARGETS = [
336
+ {
337
+ workflow: '.github/workflows/clean-ga-workflows.yml',
338
+ namespace: 'ga.clean_ga_workflows',
339
+ policyDirRel: 'ga/clean_ga_workflows'
340
+ },
341
+ {
342
+ workflow: '.github/workflows/clean-merged-branch.yml',
343
+ namespace: 'ga.clean_merged_branch',
344
+ policyDirRel: 'ga/clean_merged_branch'
345
+ },
346
+ {
347
+ workflow: '.github/workflows/lint-ga.yml',
348
+ namespace: 'ga.lint_ga',
349
+ policyDirRel: 'ga/lint_ga'
350
+ },
351
+ {
352
+ workflow: '.github/workflows/git-ai.yml',
353
+ namespace: 'ga.git_ai',
354
+ policyDirRel: 'ga/git_ai'
355
+ }
356
+ ]
357
+
358
+ /**
359
+ * Plan B (rego-authoritative): на початку перевірки правила ga прогнати усі
360
+ * Rego-полісі з `npm/policy/ga/`. Спочатку — per-workflow (4 окремі спавни,
361
+ * бо кожен namespace застосовний лише до свого файла), потім один батч-спавн
362
+ * `ga.workflow_common` на всі `.github/workflows/*.yml`. Hard-fail без
363
+ * `conftest` у PATH — узгоджено з Plan B (див. `runConftestBatch`).
364
+ * @param {string} wfDir шлях до `.github/workflows`
365
+ * @param {string[]} ymlWorkflows відносні (від `wfDir`) імена файлів `*.yml`
366
+ * @param {(msg: string) => void} pass callback при успішній перевірці
367
+ * @param {(msg: string) => void} fail callback при помилці
368
+ * @returns {Promise<void>}
369
+ */
370
+ async function runAllGaRego(wfDir, ymlWorkflows, pass, fail) {
371
+ for (const target of GA_PER_WORKFLOW_REGO_TARGETS) {
372
+ if (!existsSync(target.workflow)) continue
373
+ const violations = runConftestBatch({
374
+ policyDirRel: target.policyDirRel,
375
+ namespace: target.namespace,
376
+ files: [target.workflow]
377
+ })
378
+ for (const v of violations) fail(`${target.workflow}: ${v.message}`)
379
+ if (violations.length === 0) {
380
+ pass(`${target.workflow}: відповідає ${target.namespace} (rego)`)
381
+ }
382
+ }
383
+
384
+ if (ymlWorkflows.length === 0) return
385
+ const wfFiles = ymlWorkflows.map(f => join(wfDir, f))
386
+ const violations = runConftestBatch({
387
+ policyDirRel: 'ga/workflow_common',
388
+ namespace: 'ga.workflow_common',
389
+ files: wfFiles
390
+ })
391
+ for (const v of violations) fail(`${v.filename}: ${v.message}`)
392
+ if (violations.length === 0) {
393
+ pass(`${wfFiles.length} workflow(s) відповідають ga.workflow_common (rego)`)
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Перевіряє відповідність проєкту правилам ga.mdc.
399
+ *
400
+ * Plan B-патерн: пер-документна валідація workflow-структури делегована
401
+ * Rego-полісі у `npm/policy/ga/`; виклик через `runAllGaRego` (батч-conftest)
402
+ * — це перший крок `check()`. Далі — JS-частина (cross-file перевірки на
403
+ * наявність файлів, `git ls-files`-залежні `on.push.paths` glob, vscode/zizmor
404
+ * config, megalinter залишки тощо). `bun run lint-ga` додатково запускає
405
+ * `actionlint` + `zizmor` зовнішніми тулчейнами і **викликає цю ж `check()`** —
406
+ * тобто rego-частина живе тут, не в `lint-ga.mjs`.
329
407
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
330
408
  */
331
409
  export async function check() {
@@ -339,6 +417,13 @@ export async function check() {
339
417
  return reporter.getExitCode()
340
418
  }
341
419
 
420
+ const files = await readdir(wfDir)
421
+ const ymlWorkflows = files.filter(f => f.endsWith('.yml'))
422
+
423
+ // Rego-крок (per-workflow + workflow_common) — на початку, як єдине джерело
424
+ // істини для пер-документних структурних правил workflow-файлів.
425
+ await runAllGaRego(wfDir, ymlWorkflows, pass, fail)
426
+
342
427
  const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
343
428
  if (existsSync(setupBunDepsAction)) {
344
429
  pass(`${setupBunDepsAction} існує`)
@@ -348,7 +433,6 @@ export async function check() {
348
433
  )
349
434
  }
350
435
 
351
- const files = await readdir(wfDir)
352
436
  checkGaWorkflowFiles(wfDir, files, pass, fail)
353
437
 
354
438
  await checkApplyWorkflow(wfDir, files, 'apply-k8s.yml', '**/k8s/**/*.yaml', pass, fail)
@@ -367,14 +451,10 @@ export async function check() {
367
451
 
368
452
  await checkVscodeSettingsForGa(pass, fail)
369
453
 
370
- const ymlWorkflows = files.filter(f => f.endsWith('.yml'))
371
454
  await checkMegalinter(wfDir, ymlWorkflows, pass, fail)
372
455
 
373
- // Універсальні структурні перевірки (concurrency, заборонені setup-bun/cache,
374
- // shell line-continuation `\`, checkout перед локальним setup-bun-deps)
375
- // перенесено в Rego (`npm/policy/ga/workflow_common/`); їх запускає
376
- // `bun run lint-ga` через conftest. Тут лишилася лише git-залежна перевірка
377
- // `on.push.paths` glob-ів (вимагає `git ls-files`).
456
+ // git-залежна перевірка `on.push.paths` glob-ів (вимагає `git ls-files`) —
457
+ // лишається в JS, бо conftest не має доступу до файлової системи репо.
378
458
  for (const f of ymlWorkflows) {
379
459
  const content = await readFile(join(wfDir, f), 'utf8')
380
460
  const parsed = parseWorkflowYaml(content)