@nitra/cursor 1.13.8 → 1.13.12

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/package.json +1 -1
  3. package/rules/ga/fix/workflows/check.mjs +12 -3
  4. package/rules/ga/ga.mdc +18 -152
  5. package/rules/ga/policy/clean_ga_workflows/clean_ga_workflows.rego +38 -49
  6. package/rules/ga/policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +26 -0
  7. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +55 -57
  8. package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +37 -0
  9. package/rules/ga/policy/git_ai/git_ai.rego +28 -35
  10. package/rules/ga/policy/git_ai/template/git-ai.yml.snippet.yml +30 -0
  11. package/rules/ga/policy/lint_ga/lint_ga.rego +46 -55
  12. package/rules/ga/policy/lint_ga/template/lint-ga.yml.snippet.yml +35 -0
  13. package/rules/ga/policy/package_json/package_json.rego +9 -13
  14. package/rules/ga/policy/package_json/template/package.json.contains.json +1 -0
  15. package/rules/ga/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  16. package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +6 -4
  17. package/rules/ga/policy/vscode_settings/template/settings.json.snippet.json +1 -0
  18. package/rules/ga/policy/vscode_settings/vscode_settings.rego +11 -13
  19. package/rules/ga/policy/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
  20. package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +16 -6
  21. package/rules/rego/lint/lint.mjs +5 -4
  22. package/rules/rego/policy/package_json/package_json.rego +8 -29
  23. package/rules/rego/policy/package_json/template/package.json.snippet.json +1 -0
  24. package/rules/rego/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  25. package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +7 -11
  26. package/rules/rego/policy/vscode_settings/template/settings.json.snippet.json +6 -0
  27. package/rules/rego/policy/vscode_settings/vscode_settings.rego +19 -27
  28. package/rules/rego/rego.mdc +10 -8
  29. package/rules/security/todo.MD +27 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,66 @@
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.12] - 2026-05-17
8
+
9
+ ### Added
10
+
11
+ - `ga` rule template/ міграція доповнена (Phase 3.5 — 4 full-canon workflow концерни, пропущені в 1.13.9): `clean_ga_workflows`, `clean_merged_branch`, `lint_ga`, `git_ai`. Кожен має повний YAML канон у `template/<workflow>.yml.snippet.yml`, rego читає expected-значення з `data.template.snippet.<path>` (path лишається у rego, literals — у template). Для кожного — `*_test.rego` із canonical/wrong/drift тестами.
12
+ - Wiring у `ga/fix/workflows/check.mjs`: `runAllGaRego` тепер `await loadTemplate(concernDir)` і передає `templateData` у `runConftestBatch` для кожного workflow концерну.
13
+
14
+ ### Changed
15
+
16
+ - `ga.mdc` — 4 inline YAML-блоки повних workflow канонів замінено на markdown-посилання до `template/<workflow>.yml.snippet.yml`. Файл скоротився суттєво — канон тепер живе як data, а не як прозовий приклад.
17
+ - `template/clean-merged-branch.yml.snippet.yml`: `dry_run: false` (явний bool) замість `dry_run: no` — `yaml` npm (YAML 1.2) лишає `no` рядком, а Go-yaml у conftest нормалізує до `false`; пишемо канонізовану форму під runtime conftest-парсингу.
18
+ - `docs/adr/template-dir-concern-inventory.md` — додано 4 нові full-canon ga.* концерни з ✓; оновлено summary (89 концернів, 43 з template — мігровано 13/43 = 30%).
19
+
20
+ ### TODO
21
+
22
+ - `ga.workflow_common` — cross-workflow forbidden-patterns (concurrency, depcheck deny, shell line-continuation). Не fully full-canon — окремий випадок, мігрується пізніше.
23
+
24
+ ## [1.13.11] - 2026-05-17
25
+
26
+ ### Added
27
+
28
+ - `rego` rule template/ міграція (Phase 3): 3 концерни — `package_json` (snippet із збереженням `trim_space` tolerance), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object 2-level + окремий deny на non-object block).
29
+ - Drift-тести у кожному `*_test.rego`.
30
+
31
+ ### Changed
32
+
33
+ - `rego.package_json.rego` — замість двох inline-deny (missing + wrong-value через `regex/trim_space`) тепер один snippet-walker через `data.template.snippet`.
34
+ - `rego.vscode_extensions.rego` — замість inline `"tsandall.opa"` тепер subset-of через `data.template.snippet.recommendations`.
35
+ - `rego.vscode_settings.rego` — 2-рівневий snippet-walker з гардом `is_object(inner)` для випадку, коли block існує, але не обʼєкт.
36
+ - `rego.mdc` — inline `package.json` snippet замінено на template-link; додано посилання на `.vscode/{extensions,settings}.json` template-файли. Виправлено застаріле `Цілі — npm/policy/` → `npm/rules/`.
37
+ - `docs/adr/template-dir-concern-inventory.md` — позначено 3 `rego.*` концерни як ✓; додано Phase 3 у прогрес-секцію.
38
+
39
+ ## [1.13.10] - 2026-05-17
40
+
41
+ ### Fixed
42
+
43
+ - `runLintRego` (`npm/rules/rego/lint/lint.mjs`) — `LINT_TARGETS` вказував на застарілий шлях `npm/policy` (не існує після Phase 1 реструктуризації), тож `bun run lint-rego` мовчки exit 0 без реальної перевірки. Тепер `LINT_TARGETS = ['npm/rules']` — `opa check --strict`, `regal lint`, `conftest verify` реально проходять по всіх 111 `.rego`-файлах. TDD-регресія у `lint.test.mjs` (broken-syntax + well-formed fixtures).
44
+
45
+ ### Changed
46
+
47
+ - `.regal/config.yaml` — додано `idiomatic.directory-package-mismatch` і `imports.unresolved-reference` у `ignore` (інтенціональні конвенції проєкту: package = `<rule>.<concern>` у `<rule>/policy/<concern>/`; `data.template.*` ін'єктиться runtime через `--data`). `style.line-length.max-line-length: 220` — узгоджено з `opa fmt` (тримає малі обʼєкти single-line).
48
+ - `*_test.rego` з порушенням `test-outside-test-package` (4 файли: `js-lint.jscpd`, `js-lint.vscode_extensions`, `security.gitleaks`, `vue.package_json`) — перейменовано в `<package>_test` із явним `import data.<package>`.
49
+ - `opa fmt -w npm/rules` — auto-fix форматування.
50
+ - `docs/adr/template-dir-concern-inventory.md` — додано 4 `ga.*` концерни з відміткою `✓` (мігровано); оновлено summary-числа (85 концернів, 39 з template — 46%); додано секцію прогресу міграції.
51
+
52
+ ## [1.13.9] - 2026-05-17
53
+
54
+ ### Added
55
+
56
+ - `ga` rule template/ міграція (Phase 2): 4 концерни — `package_json` (contains-style), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object), `zizmor_yml` (snippet з канонічним path `rules.unpinned-uses.config.policies."*"`).
57
+ - Drift-тести (`test_data_template_drives_*`) у кожному `*_test.rego` ловлять регресію, якщо rego перестане читати з `data.template`.
58
+
59
+ ### Changed
60
+
61
+ - `ga.package_json.rego` — замість двох inline-deny з `is_string` + `regex.match` тепер один generic contains-walker через `data.template.contains`.
62
+ - `ga.vscode_extensions.rego` — замість inline `"github.vscode-github-actions"` тепер subset-of через `data.template.snippet.recommendations`.
63
+ - `ga.vscode_settings.rego` — 2-рівневий snippet-walker через `data.template.snippet` (літеральні keys `[github-actions-workflow]`, `editor.defaultFormatter`).
64
+ - `ga.zizmor_yml.rego` — замість substring `json.marshal` хака тепер структурний чек `rules.unpinned-uses.config.policies."*"` із expected value з `data.template.snippet`.
65
+ - `ga.mdc` — inline `package.json` snippet і `zizmor.yml` snippet блоки замінено на markdown-посилання на template-файли; додано посилання на нові template/ для `.vscode/{extensions,settings}.json`.
66
+
7
67
  ## [1.13.8] - 2026-05-17
8
68
 
9
69
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.8",
3
+ "version": "1.13.12",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -17,12 +17,17 @@
17
17
  import { existsSync } from 'node:fs'
18
18
  import { readdir, readFile } from 'node:fs/promises'
19
19
  import { execFileSync } from 'node:child_process'
20
- import { join } from 'node:path'
20
+ import { basename, dirname, join } from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
21
22
 
22
23
  import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
23
24
  import { eventPathsIncludeExact, parseWorkflowYaml } from '../../../../scripts/utils/gha-workflow.mjs'
24
25
  import { resolveCmd } from '../../../../scripts/utils/resolve-cmd.mjs'
25
26
  import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
27
+ import { loadTemplate } from '../../../../scripts/utils/template.mjs'
28
+
29
+ const HERE = dirname(fileURLToPath(import.meta.url))
30
+ const GA_POLICY_DIR = join(HERE, '..', '..', 'policy')
26
31
 
27
32
  /** Шаблони наявності MegaLinter у вмісті workflow */
28
33
  const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/megalinter/i]
@@ -278,13 +283,17 @@ const GA_PER_WORKFLOW_REGO_TARGETS = [
278
283
  * @param {(msg: string) => void} fail callback при помилці
279
284
  * @returns {void}
280
285
  */
281
- function runAllGaRego(wfDir, ymlWorkflows, pass, fail) {
286
+ async function runAllGaRego(wfDir, ymlWorkflows, pass, fail) {
282
287
  for (const target of GA_PER_WORKFLOW_REGO_TARGETS) {
283
288
  if (!existsSync(target.workflow)) continue
289
+ const concernDir = join(GA_POLICY_DIR, target.policyDirRel.split('/')[1])
290
+ const tpl = await loadTemplate(concernDir)
291
+ const templateData = tpl[basename(target.workflow)]
284
292
  const violations = runConftestBatch({
285
293
  policyDirRel: target.policyDirRel,
286
294
  namespace: target.namespace,
287
- files: [target.workflow]
295
+ files: [target.workflow],
296
+ templateData
288
297
  })
289
298
  for (const v of violations) fail(`${target.workflow}: ${v.message}`)
290
299
  if (violations.length === 0) {
package/rules/ga/ga.mdc CHANGED
@@ -17,152 +17,23 @@ concurrency:
17
17
 
18
18
  Без винятків — у scheduled cleanup-воркфлоу, у `pull_request: types: [closed]`, у publish-воркфлоу теж. Це уникає паралельних запусків того самого workflow на тій самій ref і скасовує попередні в чергу нових.
19
19
 
20
- Повинен бути файл .github/workflows/clean-ga-workflows.yml, зі змістом:
20
+ Повинен бути файл `.github/workflows/clean-ga-workflows.yml`:
21
21
 
22
- ```yaml
23
- name: Clean action for removing completed workflow runs
24
-
25
- on:
26
- schedule:
27
- - cron: '0 1 16 * *'
28
-
29
- # Allow workflow to be manually run from the GitHub UI
30
- workflow_dispatch: {}
31
-
32
- concurrency:
33
- group: ${{ github.ref }}-${{ github.workflow }}
34
- cancel-in-progress: true
22
+ - Канон: [clean-ga-workflows.yml.snippet.yml](./policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml)
35
23
 
36
- jobs:
37
- cleanup_old_workflows:
38
- runs-on: ubuntu-latest
39
- permissions:
40
- actions: write
41
- contents: read
42
- steps:
43
- - name: Delete workflow runs
44
- uses: dmvict/clean-workflow-runs@v1
45
- with:
46
- token: ${{ github.token }}
47
- save_period: 31
48
- save_min_runs_number: 0
49
-
50
- ```
51
-
52
- Повинен бути файл .github/workflows/clean-merged-branch.yml, зі змістом:
53
-
54
- ```yaml
55
- name: Clean abandoned branches
24
+ Повинен бути файл `.github/workflows/clean-merged-branch.yml`:
56
25
 
57
- on:
58
- # Run daily at midnight
59
- schedule:
60
- - cron: '0 1 15 * *'
61
-
62
- # Allow workflow to be manually run from the GitHub UI
63
- workflow_dispatch: {}
64
-
65
- concurrency:
66
- group: ${{ github.ref }}-${{ github.workflow }}
67
- cancel-in-progress: true
68
-
69
- jobs:
70
- cleanup_old_branches:
71
- runs-on: ubuntu-latest
72
- permissions:
73
- contents: write
74
- steps:
75
- - id: delete_stuff
76
- name: Delete those pesky dead branches
77
- uses: phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3
78
- with:
79
- github_token: ${{ github.token }}
80
- last_commit_age_days: 90
81
- ignore_branches: main,dev
82
- dry_run: no
83
-
84
- - name: Get output
85
- env:
86
- DELETED_BRANCHES: ${{ steps.delete_stuff.outputs.deleted_branches }}
87
- run: |
88
- echo "Deleted branches: ${DELETED_BRANCHES}"
89
- ```
26
+ - Канон: [clean-merged-branch.yml.snippet.yml](./policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml)
90
27
 
91
28
  Інші гілки в `ignore_branches` — допустимо.
92
- Повинен бути файл .github/workflows/lint-ga.yml, зі змістом:
93
29
 
94
- ```yaml
95
- name: Lint GA
96
-
97
- on:
98
- push:
99
- branches:
100
- - dev
101
- - main
102
- paths:
103
- - '.github/actions/**'
104
- - '.github/workflows/**'
105
- pull_request:
106
- branches:
107
- - dev
108
- - main
30
+ Повинен бути файл `.github/workflows/lint-ga.yml`:
109
31
 
110
- concurrency:
111
- group: ${{ github.ref }}-${{ github.workflow }}
112
- cancel-in-progress: true
32
+ - Канон: [lint-ga.yml.snippet.yml](./policy/lint_ga/template/lint-ga.yml.snippet.yml)
113
33
 
114
- jobs:
115
- lint-ga:
116
- runs-on: ubuntu-latest
117
- permissions:
118
- contents: read
119
- steps:
120
- - uses: actions/checkout@v6
121
- with:
122
- persist-credentials: false
123
-
124
- - uses: ./.github/actions/setup-bun-deps
125
-
126
- - uses: astral-sh/setup-uv@v8.0.0
127
-
128
- - name: Lint GA
129
- run: bun run lint-ga
130
- ```
131
-
132
- Повинен бути файл .github/workflows/git-ai.yml, зі змістом:
133
-
134
- ```yaml
135
- name: Git AI
136
-
137
- on:
138
- pull_request:
139
- types: [closed]
34
+ Повинен бути файл `.github/workflows/git-ai.yml`:
140
35
 
141
- concurrency:
142
- group: ${{ github.ref }}-${{ github.workflow }}
143
- cancel-in-progress: true
144
-
145
- jobs:
146
- git-ai:
147
- if: github.event.pull_request.merged == true
148
- runs-on: ubuntu-latest
149
- permissions:
150
- contents: write
151
-
152
- steps:
153
- - name: Install git-ai
154
- run: |
155
- curl -fsSL https://usegitai.com/install.sh | bash
156
- echo "$HOME/.git-ai/bin" >> $GITHUB_PATH
157
- - name: Run git-ai
158
- id: run-git-ai
159
- env:
160
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
161
- run: |
162
- git config --global user.name "github-actions[bot]"
163
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
164
- git-ai ci github run
165
- ```
36
+ - Канон: [git-ai.yml.snippet.yml](./policy/git_ai/template/git-ai.yml.snippet.yml)
166
37
 
167
38
  **Локальний 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`**.
168
39
 
@@ -242,11 +113,7 @@ jobs:
242
113
 
243
114
  **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Канонічний скрипт у корені делегує виконання CLI `n-cursor lint-ga` (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`), який робить preflight на `shellcheck` і послідовно запускає `actionlint` та `zizmor`:
244
115
 
245
- ```json title="package.json"
246
- "scripts": {
247
- "lint-ga": "n-cursor lint-ga"
248
- }
249
- ```
116
+ - `package.json` — `scripts.lint-ga` має містити `n-cursor lint-ga`: [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
250
117
 
251
118
  > Не використовуй `npx --no @nitra/cursor lint-ga` — `bun run` автоматично транслює `npx` у `bun x`, а `bun x` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання. Виклик через bin-ім’я `n-cursor` працює і у `bun run`, і у `npm run`.
252
119
 
@@ -254,16 +121,15 @@ CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, поті
254
121
 
255
122
  **`.github/zizmor.yml`:** для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection):
256
123
 
257
- ```yaml title=".github/zizmor.yml"
258
- # https://docs.zizmor.sh/configuration/
259
- rules:
260
- unpinned-uses:
261
- config:
262
- policies:
263
- '*': ref-pin
264
- template-injection:
265
- disable: true
266
- ```
124
+ - Канон `.github/zizmor.yml`: [zizmor.yml.snippet.yml](./policy/zizmor_yml/template/zizmor.yml.snippet.yml)
125
+
126
+ **`.vscode/extensions.json`** має рекомендувати `github.vscode-github-actions`:
127
+
128
+ - Канон: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
129
+
130
+ **`.vscode/settings.json`** для мови `github-actions-workflow` має `editor.defaultFormatter = "oxc.oxc-vscode"`:
131
+
132
+ - Канон: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
267
133
 
268
134
  **MegaLinter:** не використовувати; прибрати workflow, конфіги (`.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`), залежності та згадки в CI / pre-commit / документації.
269
135
 
@@ -1,42 +1,33 @@
1
- # Порт перевірки `validateCleanGaWorkflows` з `npm/scripts/check-ga.mjs` (ga.mdc).
1
+ # Перевірка `.github/workflows/clean-ga-workflows.yml` (ga.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .github/workflows/clean-ga-workflows.yml \
5
- # -p npm/policy/ga --namespace ga.clean_ga_workflows
6
- #
7
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
8
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
9
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
10
- #
11
- # Усі `deny`-правила йдуть контигно (regal: messy-rule); helpers і константи —
12
- # секціями вище та нижче.
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/clean-ga-workflows.yml.snippet.yml
5
+ # (повний YAML канон). Path-and-value перевірки у цьому rego (per-concern
6
+ # field-by-field з .data.template.snippet.<path>); жодних inline literals.
13
7
  package ga.clean_ga_workflows
14
8
 
15
9
  import rego.v1
16
10
 
17
- # ── Очікувані значення ─────────────────────────────────────────────────────
18
- #
19
- # `${{ }}` шаблонний синтаксис GitHub Actions; `{{` у Rego починає string
20
- # interpolation. Збираємо очікувані рядки з фрагментів через `concat`, як це
21
- # зроблено в check-ga.mjs, щоб і Rego-парсер, і людина-читач не плуталися.
11
+ # ── Аліаси на input ────────────────────────────────────────────────────────
12
+ # GHA YAML quirk: ключ `on:` — YAML 1.1 boolean `true`, у conftest серіалізується
13
+ # як рядковий ключ "true". Template (через --data JSON) має ключ "on".
22
14
 
23
- expected_github_token := concat("", ["$", "{{ github.token }}"])
15
+ gha_on := input["true"]
24
16
 
25
- expected_name := "Clean action for removing completed workflow runs"
17
+ step0 := input.jobs.cleanup_old_workflows.steps[0]
26
18
 
27
- expected_cron := "0 1 16 * *"
19
+ # Експектації з template.
20
+ expected_name := data.template.snippet.name
28
21
 
29
- # ── Аліаси на input ────────────────────────────────────────────────────────
30
- #
31
- # GHA YAML quirk: ключ `on:` — YAML 1.1 boolean `true`, конфтест серіалізує його
32
- # як рядковий ключ "true". Ані `input.on`, ані `input["on"]`, ані `input[true]`
33
- # не працюють — лише `input["true"]`.
22
+ expected_cron := data.template.snippet.on.schedule[0].cron
34
23
 
35
- gha_on := input["true"]
24
+ expected_step0 := data.template.snippet.jobs.cleanup_old_workflows.steps[0]
36
25
 
37
- step0 := input.jobs.cleanup_old_workflows.steps[0]
26
+ expected_perms := data.template.snippet.jobs.cleanup_old_workflows.permissions
38
27
 
39
- # ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
28
+ expected_runs_on := data.template.snippet.jobs.cleanup_old_workflows["runs-on"]
29
+
30
+ # ── deny rules ─────────────────────────────────────────────────────────────
40
31
 
41
32
  deny contains msg if {
42
33
  input.name != expected_name
@@ -49,7 +40,7 @@ deny contains msg if {
49
40
  }
50
41
 
51
42
  deny contains msg if {
52
- not has_workflow_dispatch
43
+ not is_object(object.get(gha_on, "workflow_dispatch", null))
53
44
  msg := "clean-ga-workflows.yml: має бути workflow_dispatch: {} (ga.mdc)"
54
45
  }
55
46
 
@@ -60,32 +51,34 @@ deny contains msg if {
60
51
 
61
52
  deny contains msg if {
62
53
  job := input.jobs.cleanup_old_workflows
63
- job["runs-on"] != "ubuntu-latest"
64
- msg := "clean-ga-workflows.yml: runs-on має бути ubuntu-latest (ga.mdc)"
54
+ job["runs-on"] != expected_runs_on
55
+ msg := sprintf("clean-ga-workflows.yml: runs-on має бути %s (ga.mdc)", [expected_runs_on])
65
56
  }
66
57
 
67
58
  deny contains msg if {
68
59
  perms := input.jobs.cleanup_old_workflows.permissions
69
- not actions_write_contents_read(perms)
60
+ not perms_match(perms, expected_perms)
70
61
  msg := "clean-ga-workflows.yml: permissions мають бути actions: write, contents: read (ga.mdc)"
71
62
  }
72
63
 
73
64
  deny contains msg if {
74
- step0.name != "Delete workflow runs"
75
- msg := "clean-ga-workflows.yml: перший крок має мати name: Delete workflow runs (ga.mdc)"
65
+ step0.name != expected_step0.name
66
+ msg := sprintf("clean-ga-workflows.yml: перший крок має мати name: %s (ga.mdc)", [expected_step0.name])
76
67
  }
77
68
 
78
69
  deny contains msg if {
79
- step0.uses != "dmvict/clean-workflow-runs@v1"
80
- msg := "clean-ga-workflows.yml: перший крок має uses: dmvict/clean-workflow-runs@v1 (ga.mdc)"
70
+ step0.uses != expected_step0.uses
71
+ msg := sprintf("clean-ga-workflows.yml: перший крок має uses: %s (ga.mdc)", [expected_step0.uses])
81
72
  }
82
73
 
83
- # Триплет полів `with`: token (gh-токен), save_period=31, save_min_runs_number=0.
84
- # В JS-перевірці помилка спільна для всіх трьох — лишаємо такий самий формат, щоб
85
- # повідомлення збігалися.
86
74
  deny contains msg if {
87
75
  not step0_with_canonical
88
- msg := "clean-ga-workflows.yml: with має містити token/save_period/save_min_runs_number як у ga.mdc"
76
+ msg := "clean-ga-workflows.yml: with має містити token/save_period/save_min_runs_number як у template (ga.mdc)"
77
+ }
78
+
79
+ deny contains msg if {
80
+ step0.with.save_period != expected_step0.with.save_period
81
+ msg := sprintf("clean-ga-workflows.yml: with.save_period має бути %d (ga.mdc)", [expected_step0.with.save_period])
89
82
  }
90
83
 
91
84
  # ── helpers ────────────────────────────────────────────────────────────────
@@ -94,17 +87,13 @@ has_expected_cron if {
94
87
  gha_on.schedule[_].cron == expected_cron
95
88
  }
96
89
 
97
- has_workflow_dispatch if {
98
- is_object(gha_on.workflow_dispatch)
99
- }
100
-
101
- actions_write_contents_read(perms) if {
102
- perms.actions == "write"
103
- perms.contents == "read"
90
+ perms_match(actual, expected) if {
91
+ actual.actions == expected.actions
92
+ actual.contents == expected.contents
104
93
  }
105
94
 
106
95
  step0_with_canonical if {
107
- step0.with.token == expected_github_token
108
- step0.with.save_period == 31
109
- step0.with.save_min_runs_number == 0
96
+ step0.with.token == expected_step0.with.token
97
+ step0.with.save_period == expected_step0.with.save_period
98
+ step0.with.save_min_runs_number == expected_step0.with.save_min_runs_number
110
99
  }
@@ -0,0 +1,26 @@
1
+ name: Clean action for removing completed workflow runs
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 1 16 * *'
6
+
7
+ # Allow workflow to be manually run from the GitHub UI
8
+ workflow_dispatch: {}
9
+
10
+ concurrency:
11
+ group: ${{ github.ref }}-${{ github.workflow }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ cleanup_old_workflows:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ actions: write
19
+ contents: read
20
+ steps:
21
+ - name: Delete workflow runs
22
+ uses: dmvict/clean-workflow-runs@v1
23
+ with:
24
+ token: ${{ github.token }}
25
+ save_period: 31
26
+ save_min_runs_number: 0