@nitra/cursor 1.8.203 → 1.8.206
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 +35 -1
- package/bin/auto-rules.md +2 -0
- package/mdc/rego.mdc +77 -0
- package/package.json +1 -1
- package/policy/ga/{clean-ga-workflows.rego → clean_ga_workflows/clean_ga_workflows.rego} +49 -46
- package/policy/ga/clean_merged_branch/clean_merged_branch.rego +167 -0
- package/policy/ga/git_ai/git_ai.rego +109 -0
- package/policy/ga/lint_ga/lint_ga.rego +144 -0
- package/scripts/auto-rules.mjs +10 -0
- package/scripts/check-adr.mjs +4 -1
- package/scripts/check-ga.mjs +0 -504
- package/scripts/check-hasura.mjs +3 -3
- package/scripts/check-js-run.mjs +1 -4
- package/scripts/check-k8s.mjs +2 -1
- package/scripts/lint-ga.mjs +27 -5
- package/scripts/lint-rego.mjs +67 -21
- package/scripts/run-shellcheck-text.mjs +1 -4
- package/scripts/utils/depcheck-workflow.mjs +2 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.8.206] - 2026-05-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `mdc/rego.mdc` (нова версія 1.1, з 1.0): VS Code-секція з рекомендованим розширенням `tsandall.opa` (LSP від автора OPA: підсвічування, hover, go-to-definition, format-on-save через `opa fmt`), `.vscode/extensions.json` і `.vscode/settings.json` сніпети для `[rego]` (`editor.defaultFormatter: tsandall.opa`, `formatOnSave: true`); опис кроків `lint-rego` (preflight `opa`+`regal`, далі `opa check --strict` і `regal lint`); `package.json`-сніпет зі скриптом `lint-rego`; install-команди (`brew install opa regal` + universal лінки); приклад `.regal/config.yaml`. Раніше файл містив лише placeholder `npx @nitra/cursor check rego`.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `scripts/lint-rego.mjs`: додано preflight на `opa` (поряд з `regal`) з install-hint `brew install opa` і покликом до VS Code-розширення `tsandall.opa`; до `regal lint` додано попередній крок `opa check --strict <targets>` (типи + строгий режим: мертвий код, неоднозначні правила, незадекларовані змінні) — `opa check` ловить compile-помилки, які `regal` навмисно лишає поза скоупом. Якщо хоч один з `opa`/`regal` відсутній у `PATH` — exit 1 ще до запуску, з підказкою встановлення для обох.
|
|
16
|
+
|
|
17
|
+
## [1.8.205] - 2026-05-08
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `npm/policy/ga/lint_ga/lint_ga.rego` — порт `validateLintGaWorkflowStructure` + `validateLintGaOnTriggers`: `name` / `on.push.branches∋{dev,main}` / `on.pull_request.branches∋{dev,main}` / `on.push.paths∋{.github/actions/**,.github/workflows/**}` / `concurrency` / `jobs.lint-ga.runs-on` / `jobs.lint-ga.permissions.contents=read` / `steps` non-empty / `uses` set містить `actions/checkout@v6`, `./.github/actions/setup-bun-deps`, `astral-sh/setup-uv@v8.0.0` / `run` blob містить `bun run lint-ga`.
|
|
22
|
+
- `npm/policy/ga/git_ai/git_ai.rego` — порт `validateGitAiWorkflowStructure`: `name` / `on.pull_request.types∋closed` / `concurrency` / `jobs.git-ai.if` містить `merged == true` / `permissions.contents=write` / `run` blob містить `curl … usegitai.com … bash` і `git-ai ci github run`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `scripts/lint-ga.mjs`: `CONFTEST_TARGETS` тепер охоплює всі 4 канонічні GA-workflow — `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `lint-ga.yml`, `git-ai.yml` — кожен зі своїм `--namespace ga.<name>`.
|
|
27
|
+
- `scripts/check-ga.mjs`: видалено `validateLintGaWorkflowStructure`, `validateLintGaOnTriggers`, `validateGitAiWorkflowStructure`, `validateGitAiParsedYaml`, `hasPullRequestClosedTrigger`, `hasJobMergedCondition`, `checkLintGaWorkflow`, `checkGitAiWorkflow`, `checkCanonicalWorkflowsMatchRule`, локальний `isExactString` і відповідні імпорти `anyRunStepIncludes`/`flattenWorkflowSteps`/`getStepRun`/`getStepUses`. Файл скоротився з 1074 → 570 рядків (≈47%) — структурні перевірки канонічних GA-workflow повністю мігрували в conftest. У JS лишилися: file-existence (zizmor.yml, .vscode/settings.json, setup-bun-deps), `package.json` script `lint-ga`, MegaLinter-зачистка, `verifyConcurrencyBlock` для всіх workflow без винятків (включно з не-канонічними), `verifyNoDirectBunOrCache`, `verifyCheckoutBeforeLocalSetupBunDeps`, paths-globs через `git ls-files`, preflight `shellcheck`.
|
|
28
|
+
|
|
29
|
+
## [1.8.204] - 2026-05-07
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Реструктурував `npm/policy/ga/` під namespaced sub-packages, які проходять regal: `ga/clean_ga_workflows/clean_ga_workflows.rego` та новий `ga/clean_merged_branch/clean_merged_branch.rego` (порт `validateCleanMergedBranch` з check-ga.mjs — `name` / `cron 0 1 15 * *` / `workflow_dispatch` / `concurrency` / `jobs.cleanup_old_branches` / step0 `phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3` з token / age=90 / ignore_branches main,dev / `dry_run: false` (YAML 1.1) / step1 `Get output` + `DELETED_BRANCHES` env + echo).
|
|
34
|
+
- `scripts/lint-ga.mjs`: `CONFTEST_TARGETS` тепер містить `clean-ga-workflows.yml` і `clean-merged-branch.yml`, conftest викликаємо з `--namespace ga.<name>` для ізоляції правил між workflow.
|
|
35
|
+
- `scripts/check-ga.mjs`: видалено `validateCleanGaWorkflows*` і `validateCleanMergedBranch*` — їх повністю покриває conftest у `lint-ga`. `checkCanonicalWorkflowsMatchRule` тепер валідує лише `lint-ga.yml` і `git-ai.yml` (наступні кандидати на міграцію).
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- `.regal/config.yaml` у корені — вимикає `idiomatic.no-defined-entrypoint` (для conftest-полісі `deny`-правила є де-факто entrypoint-ами, формальна анотація не несе семантики).
|
|
40
|
+
|
|
7
41
|
## [1.8.203] - 2026-05-07
|
|
8
42
|
|
|
9
43
|
### Changed
|
|
@@ -108,7 +142,7 @@
|
|
|
108
142
|
### Added
|
|
109
143
|
|
|
110
144
|
- `run-shellcheck-text.mjs`: для `lint-text` — перевірка наявності `shellcheck`/`patch`, авто-виправлення через `shellcheck -f diff` + `patch -p1`, фінальний прогін по tracked `*.sh` (git) або `**/*.sh` без `node_modules`.
|
|
111
|
-
- `text` (mdc v1.25 → v1.26): **shellcheck** у ланцюжку `lint-text`, рекомендація **`timonwong.shellcheck`**, тригер workflow
|
|
145
|
+
- `text` (mdc v1.25 → v1.26): **shellcheck** у ланцюжку `lint-text`, рекомендація **`timonwong.shellcheck`**, тригер workflow **`**/\*.sh`**; тести `run-shellcheck-text.test.mjs`.
|
|
112
146
|
|
|
113
147
|
### Changed
|
|
114
148
|
|
package/bin/auto-rules.md
CHANGED
package/mdc/rego.mdc
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Opa, Rego — інструментарій (VS Code, lint-rego)
|
|
3
|
+
version: '1.1'
|
|
4
|
+
globs: "**/*.rego"
|
|
5
|
+
alwaysApply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Rego (opa)
|
|
9
|
+
|
|
10
|
+
Синтаксичні правила (`rego.v1`, `import rego.v1`, заборона legacy v0) — у `conftest.mdc` (alwaysApply). Цей файл — про **інструментарій**: VS Code, лінтери, форматування.
|
|
11
|
+
|
|
12
|
+
## VS Code
|
|
13
|
+
|
|
14
|
+
Розширення `tsandall.opa` (від автора OPA): підсвічування, hover, go-to-definition, оцінка виразів і `format-on-save` через `opa fmt`. Працює лише за наявності `opa` у `PATH` — встановити нижче.
|
|
15
|
+
|
|
16
|
+
```json title=".vscode/extensions.json"
|
|
17
|
+
{
|
|
18
|
+
"recommendations": ["tsandall.opa"]
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```json title=".vscode/settings.json"
|
|
23
|
+
{
|
|
24
|
+
"[rego]": {
|
|
25
|
+
"editor.defaultFormatter": "tsandall.opa",
|
|
26
|
+
"editor.formatOnSave": true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`opa.checkOnSave` за замовчуванням увімкнено в розширенні — діагностика від `opa check` показується в редакторі, тож синтаксичні/типові помилки видно одразу, без запуску `lint-rego`.
|
|
32
|
+
|
|
33
|
+
## Перевірка
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bun run lint-rego
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Скрипт делегує до `bun ./npm/scripts/lint-rego.mjs`. Послідовність:
|
|
40
|
+
|
|
41
|
+
1. **preflight** — наявність `opa` і `regal` у `PATH`; якщо хоча б одного нема — exit 1 з підказкою встановлення;
|
|
42
|
+
2. `opa check --strict <targets>` — компіляція з типами та `--strict` (мертвий код, неоднозначні правила, незадекларовані змінні);
|
|
43
|
+
3. `regal lint <targets>` — статичний лінтер Rego ([Styra Regal](https://docs.styra.com/regal)): ловить v0-синтаксис, неявні set-rules і відхилення від `rego.v1`, плюс bugs/idiomatic/style-правила.
|
|
44
|
+
|
|
45
|
+
Цілі — `npm/policy/` (де живуть Rego-полісі пакета). Інші *.rego поза деревом додай у `LINT_TARGETS` у `lint-rego.mjs`.
|
|
46
|
+
|
|
47
|
+
### Встановлення інструментів
|
|
48
|
+
|
|
49
|
+
- macOS: `brew install opa regal`
|
|
50
|
+
- Linux/Windows:
|
|
51
|
+
- opa — <https://www.openpolicyagent.org/docs/latest/#1-download-opa>
|
|
52
|
+
- regal — <https://docs.styra.com/regal#installation>
|
|
53
|
+
|
|
54
|
+
Обидва — лише в `PATH`, **не** додавай у `dependencies` / `devDependencies` (як `shellcheck` у `text.mdc`).
|
|
55
|
+
|
|
56
|
+
### `package.json`
|
|
57
|
+
|
|
58
|
+
```json title="package.json"
|
|
59
|
+
{
|
|
60
|
+
"scripts": {
|
|
61
|
+
"lint-rego": "bun ./npm/scripts/lint-rego.mjs"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
У кореневому `lint` (з `text.mdc` і дотичних) включай `bun run lint-rego` — щоб локальний прогін співпадав з CI.
|
|
67
|
+
|
|
68
|
+
## Конфіг regal
|
|
69
|
+
|
|
70
|
+
У корені — `.regal/config.yaml`. Дозволено вимикати окремі правила під специфіку репо (наприклад, conftest-полісі — `deny`-правила як де-факто entrypoint-и):
|
|
71
|
+
|
|
72
|
+
```yaml title=".regal/config.yaml"
|
|
73
|
+
rules:
|
|
74
|
+
idiomatic:
|
|
75
|
+
no-defined-entrypoint:
|
|
76
|
+
level: ignore
|
|
77
|
+
```
|
package/package.json
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Порт перевірки `validateCleanGaWorkflows` з `npm/scripts/check-ga.mjs` (ga.mdc).
|
|
2
2
|
#
|
|
3
3
|
# Запуск (локально):
|
|
4
|
-
# conftest test .github/workflows/clean-ga-workflows.yml
|
|
4
|
+
# conftest test .github/workflows/clean-ga-workflows.yml \
|
|
5
|
+
# -p npm/policy/ga --namespace ga.clean_ga_workflows
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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).
|
|
8
10
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
package
|
|
11
|
+
# Усі `deny`-правила йдуть контигно (regal: messy-rule); helpers і константи —
|
|
12
|
+
# секціями вище та нижче.
|
|
13
|
+
package ga.clean_ga_workflows
|
|
12
14
|
|
|
13
15
|
import rego.v1
|
|
14
16
|
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `
|
|
18
|
-
#
|
|
19
|
-
|
|
17
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
18
|
+
#
|
|
19
|
+
# `${{ … }}` — шаблонний синтаксис GitHub Actions; `{{` у Rego починає string
|
|
20
|
+
# interpolation. Збираємо очікувані рядки з фрагментів через `concat`, як це
|
|
21
|
+
# зроблено в check-ga.mjs, щоб і Rego-парсер, і людина-читач не плуталися.
|
|
20
22
|
|
|
21
|
-
# `${{ … }}` — це шаблонний синтаксис GitHub Actions, але `{{` у Rego починає
|
|
22
|
-
# string interpolation. Збираємо очікувані рядки з фрагментів, як це зроблено в
|
|
23
|
-
# check-ga.mjs, щоб і Rego-парсер, і людина-читач не плуталися.
|
|
24
23
|
expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
|
|
25
24
|
|
|
26
25
|
expected_github_token := concat("", ["$", "{{ github.token }}"])
|
|
@@ -29,43 +28,43 @@ expected_name := "Clean action for removing completed workflow runs"
|
|
|
29
28
|
|
|
30
29
|
expected_cron := "0 1 16 * *"
|
|
31
30
|
|
|
32
|
-
#
|
|
31
|
+
# Шаблон повідомлення про відсутню `concurrency`-секцію — винесено через `concat`,
|
|
32
|
+
# щоб дотриматися regal style/line-length.
|
|
33
|
+
concurrency_missing_template := concat(" ", [
|
|
34
|
+
"clean-ga-workflows.yml: відсутня секція concurrency —",
|
|
35
|
+
"додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
39
|
+
#
|
|
40
|
+
# GHA YAML quirk: ключ `on:` — YAML 1.1 boolean `true`, конфтест серіалізує його
|
|
41
|
+
# як рядковий ключ "true". Ані `input.on`, ані `input["on"]`, ані `input[true]`
|
|
42
|
+
# не працюють — лише `input["true"]`.
|
|
43
|
+
|
|
44
|
+
gha_on := input["true"]
|
|
45
|
+
|
|
46
|
+
step0 := input.jobs.cleanup_old_workflows.steps[0]
|
|
47
|
+
|
|
48
|
+
# ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
|
|
33
49
|
|
|
34
50
|
deny contains msg if {
|
|
35
51
|
input.name != expected_name
|
|
36
52
|
msg := sprintf("clean-ga-workflows.yml: name має бути %q (ga.mdc)", [expected_name])
|
|
37
53
|
}
|
|
38
54
|
|
|
39
|
-
# --- on.schedule.cron --------------------------------------------------------
|
|
40
|
-
|
|
41
55
|
deny contains msg if {
|
|
42
56
|
not has_expected_cron
|
|
43
57
|
msg := sprintf("clean-ga-workflows.yml: on.schedule має містити cron: '%s' (ga.mdc)", [expected_cron])
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
has_expected_cron if {
|
|
47
|
-
gha_on.schedule[_].cron == expected_cron
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
# --- on.workflow_dispatch ----------------------------------------------------
|
|
51
|
-
|
|
52
60
|
deny contains msg if {
|
|
53
61
|
not has_workflow_dispatch
|
|
54
62
|
msg := "clean-ga-workflows.yml: має бути workflow_dispatch: {} (ga.mdc)"
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
has_workflow_dispatch if {
|
|
58
|
-
is_object(gha_on.workflow_dispatch)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# --- concurrency -------------------------------------------------------------
|
|
62
|
-
|
|
63
65
|
deny contains msg if {
|
|
64
66
|
not is_object(input.concurrency)
|
|
65
|
-
msg := sprintf(
|
|
66
|
-
"clean-ga-workflows.yml: відсутня секція concurrency — додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
67
|
-
[expected_concurrency_group],
|
|
68
|
-
)
|
|
67
|
+
msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
deny contains msg if {
|
|
@@ -80,8 +79,6 @@ deny contains msg if {
|
|
|
80
79
|
msg := "clean-ga-workflows.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
# --- jobs.cleanup_old_workflows ---------------------------------------------
|
|
84
|
-
|
|
85
82
|
deny contains msg if {
|
|
86
83
|
not input.jobs.cleanup_old_workflows
|
|
87
84
|
msg := "clean-ga-workflows.yml: jobs.cleanup_old_workflows відсутній (ga.mdc)"
|
|
@@ -99,15 +96,6 @@ deny contains msg if {
|
|
|
99
96
|
msg := "clean-ga-workflows.yml: permissions мають бути actions: write, contents: read (ga.mdc)"
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
actions_write_contents_read(perms) if {
|
|
103
|
-
perms.actions == "write"
|
|
104
|
-
perms.contents == "read"
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# --- jobs.cleanup_old_workflows.steps[0] ------------------------------------
|
|
108
|
-
|
|
109
|
-
step0 := input.jobs.cleanup_old_workflows.steps[0]
|
|
110
|
-
|
|
111
99
|
deny contains msg if {
|
|
112
100
|
step0.name != "Delete workflow runs"
|
|
113
101
|
msg := "clean-ga-workflows.yml: перший крок має мати name: Delete workflow runs (ga.mdc)"
|
|
@@ -120,12 +108,27 @@ deny contains msg if {
|
|
|
120
108
|
|
|
121
109
|
# Триплет полів `with`: token (gh-токен), save_period=31, save_min_runs_number=0.
|
|
122
110
|
# В JS-перевірці помилка спільна для всіх трьох — лишаємо такий самий формат, щоб
|
|
123
|
-
# повідомлення збігалися.
|
|
111
|
+
# повідомлення збігалися.
|
|
124
112
|
deny contains msg if {
|
|
125
113
|
not step0_with_canonical
|
|
126
114
|
msg := "clean-ga-workflows.yml: with має містити token/save_period/save_min_runs_number як у ga.mdc"
|
|
127
115
|
}
|
|
128
116
|
|
|
117
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
has_expected_cron if {
|
|
120
|
+
gha_on.schedule[_].cron == expected_cron
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
has_workflow_dispatch if {
|
|
124
|
+
is_object(gha_on.workflow_dispatch)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
actions_write_contents_read(perms) if {
|
|
128
|
+
perms.actions == "write"
|
|
129
|
+
perms.contents == "read"
|
|
130
|
+
}
|
|
131
|
+
|
|
129
132
|
step0_with_canonical if {
|
|
130
133
|
step0.with.token == expected_github_token
|
|
131
134
|
step0.with.save_period == 31
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Порт перевірки `validateCleanMergedBranch` з `npm/scripts/check-ga.mjs` (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .github/workflows/clean-merged-branch.yml \
|
|
5
|
+
# -p npm/policy/ga --namespace ga.clean_merged_branch
|
|
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
|
+
package ga.clean_merged_branch
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
15
|
+
#
|
|
16
|
+
# Шаблонні токени GitHub Actions (`${{ … }}`) збираємо з фрагментів через
|
|
17
|
+
# `concat`, бо `{{` у Rego починає string interpolation.
|
|
18
|
+
|
|
19
|
+
expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
|
|
20
|
+
|
|
21
|
+
expected_github_token := concat("", ["$", "{{ github.token }}"])
|
|
22
|
+
|
|
23
|
+
expected_deleted_branches_expr := concat("", ["$", "{{ steps.delete_stuff.outputs.deleted_branches }}"])
|
|
24
|
+
|
|
25
|
+
expected_echo_substring := concat("", ["echo \"Deleted branches: $", "{DELETED_BRANCHES}\""])
|
|
26
|
+
|
|
27
|
+
expected_name := "Clean abandoned branches"
|
|
28
|
+
|
|
29
|
+
expected_cron := "0 1 15 * *"
|
|
30
|
+
|
|
31
|
+
# Шаблони повідомлень — через `concat` для regal style/line-length.
|
|
32
|
+
concurrency_missing_template := concat(" ", [
|
|
33
|
+
"clean-merged-branch.yml: відсутня секція concurrency —",
|
|
34
|
+
"додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
38
|
+
#
|
|
39
|
+
# YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
|
|
40
|
+
|
|
41
|
+
gha_on := input["true"]
|
|
42
|
+
|
|
43
|
+
steps := input.jobs.cleanup_old_branches.steps
|
|
44
|
+
|
|
45
|
+
step0 := steps[0]
|
|
46
|
+
|
|
47
|
+
step1 := steps[1]
|
|
48
|
+
|
|
49
|
+
# ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
|
|
50
|
+
|
|
51
|
+
deny contains msg if {
|
|
52
|
+
input.name != expected_name
|
|
53
|
+
msg := sprintf("clean-merged-branch.yml: name має бути %q (ga.mdc)", [expected_name])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
deny contains msg if {
|
|
57
|
+
not has_expected_cron
|
|
58
|
+
msg := sprintf("clean-merged-branch.yml: on.schedule має містити cron: '%s' (ga.mdc)", [expected_cron])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
deny contains msg if {
|
|
62
|
+
not has_workflow_dispatch
|
|
63
|
+
msg := "clean-merged-branch.yml: має бути workflow_dispatch: {} (ga.mdc)"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
deny contains msg if {
|
|
67
|
+
not is_object(input.concurrency)
|
|
68
|
+
msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
deny contains msg if {
|
|
72
|
+
is_object(input.concurrency)
|
|
73
|
+
input.concurrency.group != expected_concurrency_group
|
|
74
|
+
msg := sprintf("clean-merged-branch.yml: concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deny contains msg if {
|
|
78
|
+
is_object(input.concurrency)
|
|
79
|
+
input.concurrency["cancel-in-progress"] != true
|
|
80
|
+
msg := "clean-merged-branch.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
deny contains msg if {
|
|
84
|
+
not input.jobs.cleanup_old_branches
|
|
85
|
+
msg := "clean-merged-branch.yml: jobs.cleanup_old_branches відсутній (ga.mdc)"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
deny contains msg if {
|
|
89
|
+
input.jobs.cleanup_old_branches.permissions.contents != "write"
|
|
90
|
+
msg := "clean-merged-branch.yml: permissions мають бути contents: write (ga.mdc)"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
deny contains msg if {
|
|
94
|
+
count(steps) < 2
|
|
95
|
+
msg := "clean-merged-branch.yml: steps має містити 2 кроки як у ga.mdc"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ── Step 0 (delete_stuff) ──────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
deny contains msg if {
|
|
101
|
+
step0.id != "delete_stuff"
|
|
102
|
+
msg := "clean-merged-branch.yml: перший крок має id: delete_stuff (ga.mdc)"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
deny contains msg if {
|
|
106
|
+
step0.uses != "phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3"
|
|
107
|
+
msg := "clean-merged-branch.yml: перший крок має uses як у ga.mdc"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
deny contains msg if {
|
|
111
|
+
step0.with.github_token != expected_github_token
|
|
112
|
+
msg := sprintf("clean-merged-branch.yml: with.github_token має бути %s (ga.mdc)", [expected_github_token])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
deny contains msg if {
|
|
116
|
+
step0.with.last_commit_age_days != 90
|
|
117
|
+
msg := "clean-merged-branch.yml: with.last_commit_age_days має бути 90 (ga.mdc)"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
deny contains msg if {
|
|
121
|
+
not ignore_branches_has_main_and_dev
|
|
122
|
+
msg := "clean-merged-branch.yml: with.ignore_branches має містити main,dev (ga.mdc)"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# `dry_run: no` у YAML парситься як boolean `false`. JS-перевірка порівнює зі
|
|
126
|
+
# рядком "no", але в нас input уже Go-yaml-парсений — тому очікуємо `false`.
|
|
127
|
+
# (Якщо комусь схочеться явного `"no"` — треба буде брати in quotes у YAML.)
|
|
128
|
+
deny contains msg if {
|
|
129
|
+
step0.with.dry_run != false # noqa: rules-style-no-equality-with-false
|
|
130
|
+
msg := "clean-merged-branch.yml: with.dry_run має бути no (ga.mdc)"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ── Step 1 (Get output) ────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
deny contains msg if {
|
|
136
|
+
step1.name != "Get output"
|
|
137
|
+
msg := "clean-merged-branch.yml: другий крок має name: Get output (ga.mdc)"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
deny contains msg if {
|
|
141
|
+
step1.env.DELETED_BRANCHES != expected_deleted_branches_expr
|
|
142
|
+
msg := "clean-merged-branch.yml: env.DELETED_BRANCHES має бути як у ga.mdc"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
deny contains msg if {
|
|
146
|
+
not echo_deleted_branches
|
|
147
|
+
msg := "clean-merged-branch.yml: run має echo Deleted branches як у ga.mdc"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
has_expected_cron if {
|
|
153
|
+
gha_on.schedule[_].cron == expected_cron
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
has_workflow_dispatch if {
|
|
157
|
+
is_object(gha_on.workflow_dispatch)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ignore_branches_has_main_and_dev if {
|
|
161
|
+
contains(step0.with.ignore_branches, "main")
|
|
162
|
+
contains(step0.with.ignore_branches, "dev")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
echo_deleted_branches if {
|
|
166
|
+
contains(step1.run, expected_echo_substring)
|
|
167
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Порт перевірки `validateGitAiWorkflowStructure` з `npm/scripts/check-ga.mjs` (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .github/workflows/git-ai.yml \
|
|
5
|
+
# -p npm/policy/ga --namespace ga.git_ai
|
|
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
|
+
package ga.git_ai
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
|
|
17
|
+
|
|
18
|
+
expected_name := "Git AI"
|
|
19
|
+
|
|
20
|
+
expected_if_substring := "github.event.pull_request.merged == true"
|
|
21
|
+
|
|
22
|
+
expected_install_substring := "curl -fsSL https://usegitai.com/install.sh | bash"
|
|
23
|
+
|
|
24
|
+
expected_run_substring := "git-ai ci github run"
|
|
25
|
+
|
|
26
|
+
# Шаблон повідомлення про відсутню `concurrency`-секцію — через `concat` для
|
|
27
|
+
# regal style/line-length.
|
|
28
|
+
concurrency_missing_template := concat(" ", [
|
|
29
|
+
"git-ai.yml: відсутня секція concurrency —",
|
|
30
|
+
"додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
34
|
+
#
|
|
35
|
+
# YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
|
|
36
|
+
|
|
37
|
+
gha_on := input["true"]
|
|
38
|
+
|
|
39
|
+
# Job-id містить дефіс — звертаємося через `[…]`. Імʼя `job` (без префіксу пакету)
|
|
40
|
+
# — щоб уникнути regal-правила `rule-name-repeats-package`.
|
|
41
|
+
job := input.jobs["git-ai"]
|
|
42
|
+
|
|
43
|
+
# Усі `run:` зі steps цього job-а, склеєні в один blob — для substring-перевірки.
|
|
44
|
+
job_run_blob := concat("\n", [run |
|
|
45
|
+
run := job.steps[_].run
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
# ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
|
|
49
|
+
|
|
50
|
+
deny contains msg if {
|
|
51
|
+
input.name != expected_name
|
|
52
|
+
msg := sprintf("git-ai.yml: name має бути %q (ga.mdc)", [expected_name])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
deny contains msg if {
|
|
56
|
+
not "closed" in {t | some t in gha_on.pull_request.types}
|
|
57
|
+
msg := "git-ai.yml: on.pull_request.types має містити closed (ga.mdc)"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
deny contains msg if {
|
|
61
|
+
not is_object(input.concurrency)
|
|
62
|
+
msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deny contains msg if {
|
|
66
|
+
is_object(input.concurrency)
|
|
67
|
+
input.concurrency.group != expected_concurrency_group
|
|
68
|
+
msg := sprintf("git-ai.yml: concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
deny contains msg if {
|
|
72
|
+
is_object(input.concurrency)
|
|
73
|
+
input.concurrency["cancel-in-progress"] != true
|
|
74
|
+
msg := "git-ai.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deny contains msg if {
|
|
78
|
+
not job
|
|
79
|
+
msg := "git-ai.yml: jobs.git-ai відсутній (ga.mdc)"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
deny contains msg if {
|
|
83
|
+
not contains(job_if_str, expected_if_substring)
|
|
84
|
+
msg := "git-ai.yml: job має містити if: github.event.pull_request.merged == true (ga.mdc)"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
deny contains msg if {
|
|
88
|
+
job.permissions.contents != "write"
|
|
89
|
+
msg := "git-ai.yml: permissions мають бути contents: write (ga.mdc)"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
deny contains msg if {
|
|
93
|
+
not contains(job_run_blob, expected_install_substring)
|
|
94
|
+
msg := "git-ai.yml: має встановлювати git-ai через curl | bash (ga.mdc)"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
deny contains msg if {
|
|
98
|
+
not contains(job_run_blob, expected_run_substring)
|
|
99
|
+
msg := "git-ai.yml: має виконувати git-ai ci github run (ga.mdc)"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
# `if` поле job-а може бути відсутнім — тоді `sprintf` дає невизначене значення
|
|
105
|
+
# і спрацьовує `default`, повертаючи порожній рядок; `contains(…)` нижче дасть
|
|
106
|
+
# false і відповідне `deny`-правило спрацює зі зрозумілим повідомленням.
|
|
107
|
+
default job_if_str := ""
|
|
108
|
+
|
|
109
|
+
job_if_str := sprintf("%v", [job.if])
|