@nitra/cursor 1.8.105 → 1.8.106

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.
@@ -50,6 +50,91 @@ async function loadNCursorRules() {
50
50
  }
51
51
  }
52
52
 
53
+ /**
54
+ * @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
55
+ * @param {Record<string, unknown>} pkg розібраний package.json
56
+ */
57
+ function checkDevDependencies(reporter, pkg) {
58
+ const { pass, fail } = reporter
59
+ const dev = pkg.devDependencies
60
+ if (dev === undefined) {
61
+ pass('Кореневий package.json без devDependencies')
62
+ return
63
+ }
64
+ if (dev === null || typeof dev !== 'object' || Array.isArray(dev)) {
65
+ fail(
66
+ 'Кореневий package.json: `devDependencies` має бути object з ключами пакетів і діапазонами версій (не null, не масив)'
67
+ )
68
+ return
69
+ }
70
+ const bad = Object.keys(/** @type {object} */ (dev)).filter(n => !isAllowedRootDevDependency(n))
71
+ if (bad.length > 0) {
72
+ fail(`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${bad.join(', ')} (bun.mdc)`)
73
+ return
74
+ }
75
+ const n = Object.keys(/** @type {object} */ (dev)).length
76
+ pass(
77
+ n === 0
78
+ ? 'Кореневі devDependencies порожні або відсутні (лише @nitra/*)'
79
+ : `Кореневі devDependencies: лише @nitra/* (${n} пак.)`
80
+ )
81
+ }
82
+
83
+ /**
84
+ * @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
85
+ * @param {Record<string, string>} scripts scripts з package.json
86
+ */
87
+ function checkLintAggregate(reporter, scripts) {
88
+ const { pass, fail } = reporter
89
+ const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
90
+ if (lintPrefixed.length === 0) return
91
+ const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
92
+ if (!aggregate.trim()) {
93
+ const scriptList = lintPrefixed.map(s => `\`${s}\``).join(', ')
94
+ fail(
95
+ `У package.json є скрипти ${scriptList}, але немає агрегованого \`lint\` — додай скрипт, який запускає їх через \`bun run\``
96
+ )
97
+ return
98
+ }
99
+ const missing = lintPrefixed.filter(name => !aggregate.includes(`bun run ${name}`))
100
+ if (missing.length > 0) {
101
+ const missingList = missing.map(s => '`' + s + '`').join(', ')
102
+ fail(`Скрипт \`lint\` має викликати всі lint-* через bun run; відсутньо: ${missingList}`)
103
+ return
104
+ }
105
+ pass('package.json: агрегований `lint` покриває всі `lint-*` скрипти')
106
+ if (OXFMT_END_RE.test(aggregate.trim())) {
107
+ pass('package.json: `lint` завершується `&& oxfmt .`')
108
+ } else {
109
+ fail('Скрипт `lint` має закінчуватися на `&& oxfmt .`')
110
+ }
111
+ }
112
+
113
+ /**
114
+ * @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
115
+ * @param {Record<string, string>} scripts scripts з package.json
116
+ * @param {Set<string>} cursorRules активні правила з .n-cursor.json
117
+ */
118
+ function checkCursorRuleScripts(reporter, scripts, cursorRules) {
119
+ const { pass, fail } = reporter
120
+ /** @type {Array<{rule: string, script: string, doc: string}>} */
121
+ const ruleScripts = [
122
+ { rule: 'docker', script: 'lint-docker', doc: 'docker.mdc' },
123
+ { rule: 'k8s', script: 'lint-k8s', doc: 'k8s.mdc' }
124
+ ]
125
+ for (const { rule, script, doc } of ruleScripts) {
126
+ if (cursorRules.has(rule)) {
127
+ if (scripts[script]) {
128
+ pass(`package.json: є \`${script}\` (правило ${rule} у .n-cursor.json)`)
129
+ } else {
130
+ fail(
131
+ `У .n-cursor.json є правило \`${rule}\` — додай скрипт \`${script}\` у кореневий package.json (див. ${doc})`
132
+ )
133
+ }
134
+ }
135
+ }
136
+ }
137
+
53
138
  /**
54
139
  * Перевіряє відповідність проєкту правилам bun.mdc
55
140
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
@@ -58,8 +143,7 @@ export async function check() {
58
143
  const reporter = createCheckReporter()
59
144
  const { pass, fail } = reporter
60
145
 
61
- const forbidden = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']
62
- for (const f of forbidden) {
146
+ for (const f of ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']) {
63
147
  if (existsSync(f)) {
64
148
  fail(`Знайдено заборонений файл: ${f} — видали його`)
65
149
  } else {
@@ -80,86 +164,30 @@ export async function check() {
80
164
 
81
165
  const cursorRules = await loadNCursorRules()
82
166
 
83
- if (existsSync('package.json')) {
84
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
85
- if (pkg.packageManager) {
86
- fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
87
- } else {
88
- pass('package.json не містить packageManager')
89
- }
90
-
91
- if (pkg.dependencies === undefined) {
92
- pass('Кореневий package.json без поля `dependencies`')
93
- } else {
94
- fail(
95
- 'Кореневий package.json не повинен містити поле `dependencies` — додай залежності в workspace-пакети (bun.mdc)'
96
- )
97
- }
167
+ if (!existsSync('package.json')) {
168
+ return reporter.getExitCode()
169
+ }
98
170
 
99
- const dev = pkg.devDependencies
100
- if (dev === undefined) {
101
- pass('Кореневий package.json без devDependencies')
102
- } else if (dev === null || typeof dev !== 'object' || Array.isArray(dev)) {
103
- fail(
104
- 'Кореневий package.json: `devDependencies` має бути object з ключами пакетів і діапазонами версій (не null, не масив)'
105
- )
106
- } else {
107
- const bad = Object.keys(dev).filter(n => !isAllowedRootDevDependency(n))
108
- if (bad.length > 0) {
109
- fail(`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${bad.join(', ')} (bun.mdc)`)
110
- } else {
111
- const n = Object.keys(dev).length
112
- pass(
113
- n === 0
114
- ? 'Кореневі devDependencies порожні або відсутні (лише @nitra/*)'
115
- : `Кореневі devDependencies: лише @nitra/* (${n} пак.)`
116
- )
117
- }
118
- }
171
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
172
+ if (pkg.packageManager) {
173
+ fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
174
+ } else {
175
+ pass('package.json не містить packageManager')
176
+ }
119
177
 
120
- const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
178
+ if (pkg.dependencies === undefined) {
179
+ pass('Кореневий package.json без поля `dependencies`')
180
+ } else {
181
+ fail(
182
+ 'Кореневий package.json не повинен містити поле `dependencies` — додай залежності в workspace-пакети (bun.mdc)'
183
+ )
184
+ }
121
185
 
122
- if (cursorRules.has('docker')) {
123
- if (scripts['lint-docker']) {
124
- pass('package.json: є `lint-docker` (правило docker у .n-cursor.json)')
125
- } else {
126
- fail(
127
- 'У .n-cursor.json є правило `docker` — додай скрипт `lint-docker` у кореневий package.json (див. docker.mdc)'
128
- )
129
- }
130
- }
186
+ checkDevDependencies(reporter, pkg)
131
187
 
132
- if (cursorRules.has('k8s')) {
133
- if (scripts['lint-k8s']) {
134
- pass('package.json: є `lint-k8s` (правило k8s у .n-cursor.json)')
135
- } else {
136
- fail('У .n-cursor.json є правило `k8s` — додай скрипт `lint-k8s` у кореневий package.json (див. k8s.mdc)')
137
- }
138
- }
139
- const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
140
- if (lintPrefixed.length > 0) {
141
- const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
142
- if (aggregate.trim()) {
143
- const missing = lintPrefixed.filter(name => !aggregate.includes(`bun run ${name}`))
144
- if (missing.length > 0) {
145
- const missingList = missing.map(s => `\`${s}\``).join(', ')
146
- fail(`Скрипт \`lint\` має викликати всі lint-* через bun run; відсутньо: ${missingList}`)
147
- } else {
148
- pass('package.json: агрегований `lint` покриває всі `lint-*` скрипти')
149
- if (OXFMT_END_RE.test(aggregate.trim())) {
150
- pass('package.json: `lint` завершується `&& oxfmt .`')
151
- } else {
152
- fail('Скрипт `lint` має закінчуватися на `&& oxfmt .`')
153
- }
154
- }
155
- } else {
156
- const scriptList = lintPrefixed.map(s => `\`${s}\``).join(', ')
157
- fail(
158
- `У package.json є скрипти ${scriptList}, але немає агрегованого \`lint\` — додай скрипт, який запускає їх через \`bun run\``
159
- )
160
- }
161
- }
162
- }
188
+ const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
189
+ checkCursorRuleScripts(reporter, scripts, cursorRules)
190
+ checkLintAggregate(reporter, scripts)
163
191
 
164
192
  return reporter.getExitCode()
165
193
  }
@@ -118,31 +118,135 @@ function verifyNoDirectBunOrCache(relPath, content, failFn, passFn) {
118
118
  }
119
119
 
120
120
  /**
121
- * Перевіряє відповідність проєкту правилам ga.mdc
122
- * @returns {Promise<number>} 0 все OK, 1 — є проблеми
121
+ * Перевіряє apply-workflow на наявність paths trigger.
122
+ * @param {string} wfDir директорія workflows
123
+ * @param {string[]} files список файлів у директорії
124
+ * @param {string} filename параметр filename
125
+ * @param {string} expectedPath параметр expectedPath
126
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
127
+ * @param {(msg: string) => void} failFn callback при помилці
123
128
  */
124
- export async function check() {
125
- const reporter = createCheckReporter()
126
- const { pass, fail } = reporter
129
+ async function checkApplyWorkflow(wfDir, files, filename, expectedPath, passFn, failFn) {
130
+ if (!files.includes(filename)) return
131
+ const content = await readFile(`${wfDir}/${filename}`, 'utf8')
132
+ const root = parseWorkflowYaml(content)
133
+ const ok = root ? eventPathsIncludeExact(root, 'push', expectedPath) : content.includes(expectedPath)
134
+ if (ok) {
135
+ passFn(`${filename} має правильний paths trigger`)
136
+ } else {
137
+ failFn(`${filename} не містить paths: ${expectedPath}`)
138
+ }
139
+ }
127
140
 
128
- const wfDir = '.github/workflows'
141
+ /**
142
+ * Перевіряє відсутність MegaLinter у workflows та конфіг-файлах.
143
+ * @param {string} wfDir директорія workflows
144
+ * @param {string[]} ymlWorkflows параметр ymlWorkflows
145
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
146
+ * @param {(msg: string) => void} failFn callback при помилці
147
+ */
148
+ async function checkMegalinter(wfDir, ymlWorkflows, passFn, failFn) {
149
+ let found = false
150
+ for (const f of ymlWorkflows) {
151
+ const content = await readFile(join(wfDir, f), 'utf8')
152
+ if (MEGALINTER_USE_PATTERNS.some(re => re.test(content))) {
153
+ found = true
154
+ failFn(`MegaLinter у workflow ${wfDir}/${f} — видали інтеграцію (ga.mdc: MegaLinter)`)
155
+ }
156
+ }
157
+ for (const name of MEGALINTER_CONFIG_NAMES) {
158
+ if (existsSync(name)) {
159
+ found = true
160
+ failFn(`Файл ${name} — видали конфіг MegaLinter (ga.mdc: MegaLinter)`)
161
+ }
162
+ }
163
+ if (!found) passFn('Залишків MegaLinter не виявлено')
164
+ }
129
165
 
130
- if (!existsSync(wfDir)) {
131
- fail(`Директорія ${wfDir} не існує`)
132
- return reporter.getExitCode()
166
+ /**
167
+ * Перевіряє zizmor конфіг.
168
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
169
+ * @param {(msg: string) => void} failFn callback при помилці
170
+ */
171
+ async function checkZizmor(passFn, failFn) {
172
+ const zizmorPath = '.github/zizmor.yml'
173
+ if (!existsSync(zizmorPath)) {
174
+ failFn(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
175
+ return
176
+ }
177
+ const z = await readFile(zizmorPath, 'utf8')
178
+ passFn(`${zizmorPath} існує`)
179
+ if (z.includes('ref-pin')) {
180
+ passFn(`${zizmorPath} містить політику ref-pin (zizmor)`)
181
+ } else {
182
+ failFn(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
133
183
  }
184
+ }
134
185
 
135
- const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
136
- if (existsSync(setupBunDepsAction)) {
137
- pass(`${setupBunDepsAction} існує`)
186
+ /**
187
+ * Перевіряє скрипт lint-ga в package.json.
188
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
189
+ * @param {(msg: string) => void} failFn callback при помилці
190
+ */
191
+ async function checkLintGaScript(passFn, failFn) {
192
+ if (!existsSync('package.json')) {
193
+ failFn('package.json не існує — потрібен lint-ga у scripts')
194
+ return
195
+ }
196
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
197
+ const lg = pkg.scripts?.['lint-ga']
198
+ if (typeof lg !== 'string') {
199
+ failFn('package.json: додай скрипт "lint-ga" (ga.mdc)')
200
+ return
201
+ }
202
+ passFn('package.json містить lint-ga')
203
+ if (lg.includes('node-actionlint')) {
204
+ passFn('lint-ga викликає node-actionlint')
138
205
  } else {
139
- fail(
140
- `Відсутній ${setupBunDepsAction} — запустіть npx @nitra/cursor або скопіюйте з пакету (ga.mdc: composite setup-bun-deps)`
141
- )
206
+ failFn('lint-ga має містити bunx node-actionlint (ga.mdc)')
207
+ }
208
+ if (lg.includes('zizmor') && lg.includes('--offline')) {
209
+ passFn('lint-ga викликає zizmor з --offline')
210
+ } else {
211
+ failFn('lint-ga має містити zizmor і --offline (ga.mdc)')
142
212
  }
213
+ }
143
214
 
144
- const files = await readdir(wfDir)
215
+ /**
216
+ * Перевіряє lint-ga.yml workflow.
217
+ * @param {string} wfDir директорія workflows
218
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
219
+ * @param {(msg: string) => void} failFn callback при помилці
220
+ */
221
+ async function checkLintGaWorkflow(wfDir, passFn, failFn) {
222
+ const lintGaWf = join(wfDir, 'lint-ga.yml')
223
+ if (!existsSync(lintGaWf)) return
224
+ const lgContent = await readFile(lintGaWf, 'utf8')
225
+ const root = parseWorkflowYaml(lgContent)
226
+ const hasBunRun = root ? anyRunStepIncludes(root, 'bun run lint-ga') : lgContent.includes('bun run lint-ga')
227
+ const hasSetupUv = root
228
+ ? hasAnyStepUsesContaining(root, ['astral-sh/setup-uv']) || lgContent.includes('astral-sh/setup-uv')
229
+ : lgContent.includes('astral-sh/setup-uv')
230
+ if (hasBunRun) {
231
+ passFn('lint-ga.yml викликає bun run lint-ga')
232
+ } else {
233
+ failFn('lint-ga.yml: крок має містити bun run lint-ga')
234
+ }
235
+ if (hasSetupUv) {
236
+ passFn('lint-ga.yml містить astral-sh/setup-uv')
237
+ } else {
238
+ failFn('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
239
+ }
240
+ }
145
241
 
242
+ /**
243
+ * Перевіряє розширення workflow-файлів і наявність обов'язкових workflow.
244
+ * @param {string} wfDir шлях до директорії workflows
245
+ * @param {string[]} files список файлів у wfDir
246
+ * @param {(msg: string) => void} pass callback при успішній перевірці
247
+ * @param {(msg: string) => void} fail callback при помилці
248
+ */
249
+ function checkGaWorkflowFiles(wfDir, files, pass, fail) {
146
250
  const yamlFiles = files.filter(f => f.endsWith('.yaml'))
147
251
  if (yamlFiles.length > 0) {
148
252
  for (const f of yamlFiles) {
@@ -159,31 +263,38 @@ export async function check() {
159
263
  fail(`Відсутній ${wfDir}/${f}`)
160
264
  }
161
265
  }
266
+ }
162
267
 
163
- if (files.includes('apply-k8s.yml')) {
164
- const content = await readFile(`${wfDir}/apply-k8s.yml`, 'utf8')
165
- const root = parseWorkflowYaml(content)
166
- const ok =
167
- root && eventPathsIncludeExact(root, 'push', '**/k8s/**/*.yaml') ? true : content.includes('**/k8s/**/*.yaml')
168
- if (ok) {
169
- pass('apply-k8s.yml має правильний paths trigger')
170
- } else {
171
- fail('apply-k8s.yml не містить paths: **/k8s/**/*.yaml')
172
- }
268
+ /**
269
+ * Перевіряє відповідність проєкту правилам ga.mdc
270
+ * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
271
+ */
272
+ export async function check() {
273
+ const reporter = createCheckReporter()
274
+ const { pass, fail } = reporter
275
+
276
+ const wfDir = '.github/workflows'
277
+
278
+ if (!existsSync(wfDir)) {
279
+ fail(`Директорія ${wfDir} не існує`)
280
+ return reporter.getExitCode()
173
281
  }
174
282
 
175
- if (files.includes('apply-nats-consumer.yml')) {
176
- const content = await readFile(`${wfDir}/apply-nats-consumer.yml`, 'utf8')
177
- const root = parseWorkflowYaml(content)
178
- const ok =
179
- root && eventPathsIncludeExact(root, 'push', '**/consumer.yaml') ? true : content.includes('**/consumer.yaml')
180
- if (ok) {
181
- pass('apply-nats-consumer.yml має правильний paths trigger')
182
- } else {
183
- fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
184
- }
283
+ const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
284
+ if (existsSync(setupBunDepsAction)) {
285
+ pass(`${setupBunDepsAction} існує`)
286
+ } else {
287
+ fail(
288
+ `Відсутній ${setupBunDepsAction} — запустіть npx @nitra/cursor або скопіюйте з пакету (ga.mdc: composite setup-bun-deps)`
289
+ )
185
290
  }
186
291
 
292
+ const files = await readdir(wfDir)
293
+ checkGaWorkflowFiles(wfDir, files, pass, fail)
294
+
295
+ await checkApplyWorkflow(wfDir, files, 'apply-k8s.yml', '**/k8s/**/*.yaml', pass, fail)
296
+ await checkApplyWorkflow(wfDir, files, 'apply-nats-consumer.yml', '**/consumer.yaml', pass, fail)
297
+
187
298
  if (existsSync('.vscode/extensions.json')) {
188
299
  const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
189
300
  if (ext.recommendations?.includes('github.vscode-github-actions')) {
@@ -196,25 +307,7 @@ export async function check() {
196
307
  }
197
308
 
198
309
  const ymlWorkflows = files.filter(f => f.endsWith('.yml'))
199
- let foundMegalinter = false
200
- for (const f of ymlWorkflows) {
201
- const content = await readFile(join(wfDir, f), 'utf8')
202
- if (MEGALINTER_USE_PATTERNS.some(re => re.test(content))) {
203
- foundMegalinter = true
204
- fail(`MegaLinter у workflow ${wfDir}/${f} — видали інтеграцію (ga.mdc: MegaLinter)`)
205
- }
206
- }
207
-
208
- for (const name of MEGALINTER_CONFIG_NAMES) {
209
- if (existsSync(name)) {
210
- foundMegalinter = true
211
- fail(`Файл ${name} — видали конфіг MegaLinter (ga.mdc: MegaLinter)`)
212
- }
213
- }
214
-
215
- if (!foundMegalinter) {
216
- pass('Залишків MegaLinter не виявлено')
217
- }
310
+ await checkMegalinter(wfDir, ymlWorkflows, pass, fail)
218
311
 
219
312
  for (const f of ymlWorkflows) {
220
313
  const content = await readFile(join(wfDir, f), 'utf8')
@@ -222,70 +315,9 @@ export async function check() {
222
315
  verifyNoDirectBunOrCache(`${wfDir}/${f}`, content, fail, pass)
223
316
  }
224
317
 
225
- const zizmorPath = '.github/zizmor.yml'
226
- if (existsSync(zizmorPath)) {
227
- const z = await readFile(zizmorPath, 'utf8')
228
- pass(`${zizmorPath} існує`)
229
- if (z.includes('ref-pin')) {
230
- pass(`${zizmorPath} містить політику ref-pin (zizmor)`)
231
- } else {
232
- fail(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
233
- }
234
- } else {
235
- fail(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
236
- }
237
-
238
- if (existsSync('package.json')) {
239
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
240
- const lg = pkg.scripts?.['lint-ga']
241
- if (typeof lg === 'string') {
242
- pass('package.json містить lint-ga')
243
- if (lg.includes('node-actionlint')) {
244
- pass('lint-ga викликає node-actionlint')
245
- } else {
246
- fail('lint-ga має містити bunx node-actionlint (ga.mdc)')
247
- }
248
- if (lg.includes('zizmor') && lg.includes('--offline')) {
249
- pass('lint-ga викликає zizmor з --offline')
250
- } else {
251
- fail('lint-ga має містити zizmor і --offline (ga.mdc)')
252
- }
253
- } else {
254
- fail('package.json: додай скрипт "lint-ga" (ga.mdc)')
255
- }
256
- } else {
257
- fail('package.json не існує — потрібен lint-ga у scripts')
258
- }
259
-
260
- const lintGaWf = join(wfDir, 'lint-ga.yml')
261
- if (existsSync(lintGaWf)) {
262
- const lgContent = await readFile(lintGaWf, 'utf8')
263
- const root = parseWorkflowYaml(lgContent)
264
- if (root) {
265
- if (anyRunStepIncludes(root, 'bun run lint-ga')) {
266
- pass('lint-ga.yml викликає bun run lint-ga')
267
- } else {
268
- fail('lint-ga.yml: крок має містити bun run lint-ga')
269
- }
270
- const usesFlat = hasAnyStepUsesContaining(root, ['astral-sh/setup-uv'])
271
- if (usesFlat || lgContent.includes('astral-sh/setup-uv')) {
272
- pass('lint-ga.yml містить astral-sh/setup-uv')
273
- } else {
274
- fail('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
275
- }
276
- } else {
277
- if (lgContent.includes('bun run lint-ga')) {
278
- pass('lint-ga.yml викликає bun run lint-ga')
279
- } else {
280
- fail('lint-ga.yml: крок має містити bun run lint-ga')
281
- }
282
- if (lgContent.includes('astral-sh/setup-uv')) {
283
- pass('lint-ga.yml містить astral-sh/setup-uv')
284
- } else {
285
- fail('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
286
- }
287
- }
288
- }
318
+ await checkZizmor(pass, fail)
319
+ await checkLintGaScript(pass, fail)
320
+ await checkLintGaWorkflow(wfDir, pass, fail)
289
321
 
290
322
  return reporter.getExitCode()
291
323
  }