@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.
- package/CHANGELOG.md +60 -0
- package/package.json +1 -1
- package/rules/ga/fix/workflows/check.mjs +12 -3
- package/rules/ga/ga.mdc +18 -152
- package/rules/ga/policy/clean_ga_workflows/clean_ga_workflows.rego +38 -49
- package/rules/ga/policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +26 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +55 -57
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +37 -0
- package/rules/ga/policy/git_ai/git_ai.rego +28 -35
- package/rules/ga/policy/git_ai/template/git-ai.yml.snippet.yml +30 -0
- package/rules/ga/policy/lint_ga/lint_ga.rego +46 -55
- package/rules/ga/policy/lint_ga/template/lint-ga.yml.snippet.yml +35 -0
- package/rules/ga/policy/package_json/package_json.rego +9 -13
- package/rules/ga/policy/package_json/template/package.json.contains.json +1 -0
- package/rules/ga/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/ga/policy/vscode_extensions/vscode_extensions.rego +6 -4
- package/rules/ga/policy/vscode_settings/template/settings.json.snippet.json +1 -0
- package/rules/ga/policy/vscode_settings/vscode_settings.rego +11 -13
- package/rules/ga/policy/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
- package/rules/ga/policy/zizmor_yml/zizmor_yml.rego +16 -6
- package/rules/rego/lint/lint.mjs +5 -4
- package/rules/rego/policy/package_json/package_json.rego +8 -29
- package/rules/rego/policy/package_json/template/package.json.snippet.json +1 -0
- package/rules/rego/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +7 -11
- package/rules/rego/policy/vscode_settings/template/settings.json.snippet.json +6 -0
- package/rules/rego/policy/vscode_settings/vscode_settings.rego +19 -27
- package/rules/rego/rego.mdc +10 -8
- 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
|
@@ -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
|
-
Повинен бути файл
|
|
20
|
+
Повинен бути файл `.github/workflows/clean-ga-workflows.yml`:
|
|
21
21
|
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
#
|
|
1
|
+
# Перевірка `.github/workflows/clean-ga-workflows.yml` (ga.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
15
|
+
gha_on := input["true"]
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
step0 := input.jobs.cleanup_old_workflows.steps[0]
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
# Експектації з template.
|
|
20
|
+
expected_name := data.template.snippet.name
|
|
28
21
|
|
|
29
|
-
|
|
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
|
-
|
|
24
|
+
expected_step0 := data.template.snippet.jobs.cleanup_old_workflows.steps[0]
|
|
36
25
|
|
|
37
|
-
|
|
26
|
+
expected_perms := data.template.snippet.jobs.cleanup_old_workflows.permissions
|
|
38
27
|
|
|
39
|
-
|
|
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
|
|
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"] !=
|
|
64
|
-
msg := "clean-ga-workflows.yml: runs-on має бути
|
|
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
|
|
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 !=
|
|
75
|
-
msg := "clean-ga-workflows.yml: перший крок має мати name:
|
|
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 !=
|
|
80
|
-
msg := "clean-ga-workflows.yml: перший крок має uses:
|
|
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
|
-
|
|
98
|
-
|
|
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 ==
|
|
108
|
-
step0.with.save_period ==
|
|
109
|
-
step0.with.save_min_runs_number ==
|
|
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
|