@nitra/cursor 1.13.75 → 1.13.76
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,12 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.13.76] - 2026-05-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`ga.workflow_common` — мінімальні версії marketplace actions у `uses:`:** `actions/checkout` >= major `v6` (`@v6` і `@v6.0.2` дозволені), `Infisical/secrets-action` >= `v1.0.16` (канон у `policy/workflow_common/template/uses-min-versions.snippet.json`; SHA-pin пропускається). `check-ga` передає template через `--data`. Bump `ga.mdc` `1.9` → `1.10`.
|
|
12
|
+
|
|
7
13
|
## [1.13.75] - 2026-05-22
|
|
8
14
|
|
|
9
15
|
### Removed
|
package/package.json
CHANGED
|
@@ -303,10 +303,14 @@ async function runAllGaRego(wfDir, ymlWorkflows, pass, fail) {
|
|
|
303
303
|
|
|
304
304
|
if (ymlWorkflows.length === 0) return
|
|
305
305
|
const wfFiles = ymlWorkflows.map(f => join(wfDir, f))
|
|
306
|
+
const workflowCommonDir = join(GA_POLICY_DIR, 'workflow_common')
|
|
307
|
+
const workflowCommonTpl = await loadTemplate(workflowCommonDir)
|
|
308
|
+
const usesMinVersionsSnippet = workflowCommonTpl['uses-min-versions']?.snippet
|
|
306
309
|
const violations = runConftestBatch({
|
|
307
310
|
policyDirRel: 'ga/workflow_common',
|
|
308
311
|
namespace: 'ga.workflow_common',
|
|
309
|
-
files: wfFiles
|
|
312
|
+
files: wfFiles,
|
|
313
|
+
templateData: usesMinVersionsSnippet ? { snippet: usesMinVersionsSnippet } : undefined
|
|
310
314
|
})
|
|
311
315
|
for (const v of violations) fail(`${v.filename}: ${v.message}`)
|
|
312
316
|
if (violations.length === 0) {
|
package/rules/ga/ga.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила форматів для .github/workflows
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.10'
|
|
4
4
|
globs: ".github/workflows/*.yml"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -35,6 +35,13 @@ concurrency:
|
|
|
35
35
|
|
|
36
36
|
- Канон: [git-ai.yml.snippet.yml](./policy/git_ai/template/git-ai.yml.snippet.yml)
|
|
37
37
|
|
|
38
|
+
**Мінімальні версії marketplace actions** у `uses:` (перевіряє `ga.workflow_common`):
|
|
39
|
+
|
|
40
|
+
- `actions/checkout` — **не нижче major `v6`** (`@v6`, `@v6.0.2` тощо дозволені; `@v5` — ні);
|
|
41
|
+
- `Infisical/secrets-action` — **не нижче `v1.0.16`** (Node 24; нижчі теги лишаються на Node 20, deprecated з червня 2026).
|
|
42
|
+
|
|
43
|
+
Канон: [uses-min-versions.snippet.json](./policy/workflow_common/template/uses-min-versions.snippet.json). SHA-pin (40 hex) semver-політику не застосовує. Для checkout рекомендується **`v6.0.2+`** (Node 24 у action), але мінімум політики — лише major **6**.
|
|
44
|
+
|
|
38
45
|
**Локальний composite** (`uses: ./.github/actions/setup-bun-deps` або `./npm/github-actions/setup-bun-deps`): **спочатку** обов’язковий крок **`actions/checkout@v6`** (`persist-credentials: false`), інакше runner не знайде `action.yml`. Сам composite: **`actions/setup-node@v6`** (**Node 24**), **Bun**, **`actions/cache@v5`**, **`bun install --frozen-lockfile`**.
|
|
39
46
|
|
|
40
47
|
**ЗАБОРОНЕНО** дублювати кроки встановлення Bun та кешування безпосередньо у workflow файлах. Завжди використовуй локальний composite action.
|
|
@@ -55,6 +55,11 @@ setup_bun_no_checkout_template := concat(" ", [
|
|
|
55
55
|
"інакше runner не знайде action.yml (ga.mdc)",
|
|
56
56
|
])
|
|
57
57
|
|
|
58
|
+
min_uses_version_template := concat(" ", [
|
|
59
|
+
"jobs.%s.steps[%d]: %s має бути >= v%s (зараз %q) —",
|
|
60
|
+
"онови ref у uses: (ga.mdc)",
|
|
61
|
+
])
|
|
62
|
+
|
|
58
63
|
forbidden_run_command_template := concat(" ", [
|
|
59
64
|
"jobs.%s.steps[%d]: `%s` заборонено у workflow —",
|
|
60
65
|
"мігровано на knip (js-lint.mdc, ga.mdc)",
|
|
@@ -147,6 +152,28 @@ deny contains msg if {
|
|
|
147
152
|
msg := "concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
# ── deny: мінімальні версії marketplace actions у `uses:` ─────────────────
|
|
156
|
+
#
|
|
157
|
+
# Канон — `template/uses-min-versions.snippet.json` (через --data).
|
|
158
|
+
# Перевіряє semver-подібні теги `vX.Y.Z` / `vN`; SHA-pin (40 hex) пропускаємо.
|
|
159
|
+
|
|
160
|
+
deny contains msg if {
|
|
161
|
+
some entry in all_flat_steps
|
|
162
|
+
uses := object.get(entry.step, "uses", "")
|
|
163
|
+
uses != ""
|
|
164
|
+
some action_slug, min_ver in data.template.snippet
|
|
165
|
+
action_uses_matches(uses, action_slug)
|
|
166
|
+
ref := action_uses_ref(uses)
|
|
167
|
+
not action_ref_meets_min(ref, min_ver)
|
|
168
|
+
msg := sprintf(min_uses_version_template, [
|
|
169
|
+
entry.job_id,
|
|
170
|
+
entry.step_index,
|
|
171
|
+
action_slug,
|
|
172
|
+
min_ver,
|
|
173
|
+
ref,
|
|
174
|
+
])
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
# ── helpers ────────────────────────────────────────────────────────────────
|
|
151
178
|
|
|
152
179
|
# Об'єднаний рядок `uses` + `run` для одного кроку — для substring-пошуку
|
|
@@ -182,3 +209,65 @@ has_checkout_before(job, before) if {
|
|
|
182
209
|
uses := object.get(step, "uses", "")
|
|
183
210
|
contains(uses, "actions/checkout@")
|
|
184
211
|
}
|
|
212
|
+
|
|
213
|
+
# `uses:` починається з `owner/repo@` для заданого slug.
|
|
214
|
+
action_uses_matches(uses, slug) if {
|
|
215
|
+
startswith(uses, concat("", [slug, "@"]))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Ref після останнього `@` у `uses:` (owner/repo@ref).
|
|
219
|
+
action_uses_ref(uses) := ref if {
|
|
220
|
+
parts := split(uses, "@")
|
|
221
|
+
count(parts) >= 2
|
|
222
|
+
ref := parts[count(parts) - 1]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# SHA-pin — semver-політика не застосовується.
|
|
226
|
+
action_ref_is_sha_pin(ref) if {
|
|
227
|
+
regex.match(`^[0-9a-fA-F]{40}$`, ref)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Semver ref >= min (обидва як X.Y.Z після optional `v`).
|
|
231
|
+
action_ref_meets_min(ref, _) if {
|
|
232
|
+
action_ref_is_sha_pin(ref)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
action_ref_meets_min(ref, min_ver) if {
|
|
236
|
+
not action_ref_is_sha_pin(ref)
|
|
237
|
+
version_triple_gte(version_triple(ref), version_triple(min_ver))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
version_triple(raw) := [major, minor, patch] if {
|
|
241
|
+
stripped := trim_prefix(trim_prefix(raw, "v"), "V")
|
|
242
|
+
parts := split_to_numbers(stripped)
|
|
243
|
+
major := version_part(parts, 0)
|
|
244
|
+
minor := version_part(parts, 1)
|
|
245
|
+
patch := version_part(parts, 2)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
version_part(parts, idx) := parts[idx] if {
|
|
249
|
+
count(parts) > idx
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
else := 0
|
|
253
|
+
|
|
254
|
+
version_triple_gte(a, b) if {
|
|
255
|
+
a[0] > b[0]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
version_triple_gte(a, b) if {
|
|
259
|
+
a[0] == b[0]
|
|
260
|
+
a[1] > b[1]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
version_triple_gte(a, b) if {
|
|
264
|
+
a[0] == b[0]
|
|
265
|
+
a[1] == b[1]
|
|
266
|
+
a[2] >= b[2]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
split_to_numbers(spec) := nums if {
|
|
270
|
+
tokens := regex.split(`\D+`, spec)
|
|
271
|
+
non_empty := [t | some t in tokens; t != ""]
|
|
272
|
+
nums := [n | some t in non_empty; n := to_number(t)]
|
|
273
|
+
}
|