@nitra/cursor 1.13.11 → 1.13.13

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/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
35
-
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
- ```
22
+ - Канон: [clean-ga-workflows.yml.snippet.yml](./policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml)
51
23
 
52
- Повинен бути файл .github/workflows/clean-merged-branch.yml, зі змістом:
24
+ Повинен бути файл `.github/workflows/clean-merged-branch.yml`:
53
25
 
54
- ```yaml
55
- name: Clean abandoned branches
56
-
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
113
-
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
32
+ - Канон: [lint-ga.yml.snippet.yml](./policy/lint_ga/template/lint-ga.yml.snippet.yml)
127
33
 
128
- - name: Lint GA
129
- run: bun run lint-ga
130
- ```
34
+ Повинен бути файл `.github/workflows/git-ai.yml`:
131
35
 
132
- Повинен бути файл .github/workflows/git-ai.yml, зі змістом:
133
-
134
- ```yaml
135
- name: Git AI
136
-
137
- on:
138
- pull_request:
139
- types: [closed]
140
-
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
 
@@ -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
@@ -1,44 +1,32 @@
1
- # Порт перевірки `validateCleanMergedBranch` з `npm/scripts/check-ga.mjs` (ga.mdc).
1
+ # Перевірка `.github/workflows/clean-merged-branch.yml` (ga.mdc).
2
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).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/clean-merged-branch.yml.snippet.yml.
10
5
  package ga.clean_merged_branch
11
6
 
12
7
  import rego.v1
13
8
 
14
- # ── Очікувані значення ─────────────────────────────────────────────────────
15
- #
16
- # Шаблонні токени GitHub Actions (`${{ … }}`) збираємо з фрагментів через
17
- # `concat`, бо `{{` у Rego починає string interpolation.
18
-
19
- expected_github_token := concat("", ["$", "{{ github.token }}"])
9
+ # ── Аліаси ─────────────────────────────────────────────────────────────────
20
10
 
21
- expected_deleted_branches_expr := concat("", ["$", "{{ steps.delete_stuff.outputs.deleted_branches }}"])
11
+ gha_on := input["true"]
22
12
 
23
- expected_echo_substring := concat("", ["echo \"Deleted branches: $", "{DELETED_BRANCHES}\""])
13
+ steps := input.jobs.cleanup_old_branches.steps
24
14
 
25
- expected_name := "Clean abandoned branches"
15
+ step0 := steps[0]
26
16
 
27
- expected_cron := "0 1 15 * *"
17
+ step1 := steps[1]
28
18
 
29
- # ── Аліаси на input ────────────────────────────────────────────────────────
30
- #
31
- # YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
19
+ expected_name := data.template.snippet.name
32
20
 
33
- gha_on := input["true"]
21
+ expected_cron := data.template.snippet.on.schedule[0].cron
34
22
 
35
- steps := input.jobs.cleanup_old_branches.steps
23
+ expected_step0 := data.template.snippet.jobs.cleanup_old_branches.steps[0]
36
24
 
37
- step0 := steps[0]
25
+ expected_step1 := data.template.snippet.jobs.cleanup_old_branches.steps[1]
38
26
 
39
- step1 := steps[1]
27
+ expected_perms := data.template.snippet.jobs.cleanup_old_branches.permissions
40
28
 
41
- # ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
29
+ # ── deny rules ─────────────────────────────────────────────────────────────
42
30
 
43
31
  deny contains msg if {
44
32
  input.name != expected_name
@@ -51,7 +39,7 @@ deny contains msg if {
51
39
  }
52
40
 
53
41
  deny contains msg if {
54
- not has_workflow_dispatch
42
+ not is_object(object.get(gha_on, "workflow_dispatch", null))
55
43
  msg := "clean-merged-branch.yml: має бути workflow_dispatch: {} (ga.mdc)"
56
44
  }
57
45
 
@@ -61,65 +49,73 @@ deny contains msg if {
61
49
  }
62
50
 
63
51
  deny contains msg if {
64
- input.jobs.cleanup_old_branches.permissions.contents != "write"
65
- msg := "clean-merged-branch.yml: permissions мають бути contents: write (ga.mdc)"
52
+ input.jobs.cleanup_old_branches.permissions.contents != expected_perms.contents
53
+ msg := sprintf("clean-merged-branch.yml: permissions.contents має бути %s (ga.mdc)", [expected_perms.contents])
66
54
  }
67
55
 
68
56
  deny contains msg if {
69
57
  count(steps) < 2
70
- msg := "clean-merged-branch.yml: steps має містити 2 кроки як у ga.mdc"
58
+ msg := "clean-merged-branch.yml: steps має містити 2 кроки як у template (ga.mdc)"
71
59
  }
72
60
 
73
61
  # ── Step 0 (delete_stuff) ──────────────────────────────────────────────────
74
62
 
75
63
  deny contains msg if {
76
- step0.id != "delete_stuff"
77
- msg := "clean-merged-branch.yml: перший крок має id: delete_stuff (ga.mdc)"
64
+ step0.id != expected_step0.id
65
+ msg := sprintf("clean-merged-branch.yml: перший крок має id: %s (ga.mdc)", [expected_step0.id])
78
66
  }
79
67
 
80
68
  deny contains msg if {
81
- step0.uses != "phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3"
82
- msg := "clean-merged-branch.yml: перший крок має uses як у ga.mdc"
69
+ step0.uses != expected_step0.uses
70
+ msg := sprintf("clean-merged-branch.yml: перший крок має uses: %s (ga.mdc)", [expected_step0.uses])
83
71
  }
84
72
 
85
73
  deny contains msg if {
86
- step0.with.github_token != expected_github_token
87
- msg := sprintf("clean-merged-branch.yml: with.github_token має бути %s (ga.mdc)", [expected_github_token])
74
+ step0.with.github_token != expected_step0.with.github_token
75
+ msg := sprintf(
76
+ "clean-merged-branch.yml: with.github_token має бути %s (ga.mdc)",
77
+ [expected_step0.with.github_token],
78
+ )
88
79
  }
89
80
 
90
81
  deny contains msg if {
91
- step0.with.last_commit_age_days != 90
92
- msg := "clean-merged-branch.yml: with.last_commit_age_days має бути 90 (ga.mdc)"
82
+ step0.with.last_commit_age_days != expected_step0.with.last_commit_age_days
83
+ msg := sprintf(
84
+ "clean-merged-branch.yml: with.last_commit_age_days має бути %d (ga.mdc)",
85
+ [expected_step0.with.last_commit_age_days],
86
+ )
93
87
  }
94
88
 
95
89
  deny contains msg if {
96
- not ignore_branches_has_main_and_dev
97
- msg := "clean-merged-branch.yml: with.ignore_branches має містити main,dev (ga.mdc)"
90
+ not ignore_branches_subset
91
+ msg := sprintf(
92
+ "clean-merged-branch.yml: with.ignore_branches має містити %s (ga.mdc)",
93
+ [expected_step0.with.ignore_branches],
94
+ )
98
95
  }
99
96
 
100
- # `dry_run: no` у YAML парситься як boolean `false`. JS-перевірка порівнює зі
101
- # рядком "no", але в нас input уже Go-yaml-парсений тому очікуємо `false`.
102
- # (Якщо комусь схочеться явного `"no"` — треба буде брати in quotes у YAML.)
97
+ # YAML 1.1 quirk: `dry_run: no` парситься як boolean false у Go-yaml (conftest).
98
+ # Template (від `yaml` npm) теж нормалізовано до false у фікстурі.
103
99
  deny contains msg if {
104
- step0.with.dry_run != false # noqa: rules-style-no-equality-with-false
100
+ step0.with.dry_run != expected_step0.with.dry_run # noqa: rules-style-no-equality-with-false
105
101
  msg := "clean-merged-branch.yml: with.dry_run має бути no (ga.mdc)"
106
102
  }
107
103
 
108
104
  # ── Step 1 (Get output) ────────────────────────────────────────────────────
109
105
 
110
106
  deny contains msg if {
111
- step1.name != "Get output"
112
- msg := "clean-merged-branch.yml: другий крок має name: Get output (ga.mdc)"
107
+ step1.name != expected_step1.name
108
+ msg := sprintf("clean-merged-branch.yml: другий крок має name: %s (ga.mdc)", [expected_step1.name])
113
109
  }
114
110
 
115
111
  deny contains msg if {
116
- step1.env.DELETED_BRANCHES != expected_deleted_branches_expr
117
- msg := "clean-merged-branch.yml: env.DELETED_BRANCHES має бути як у ga.mdc"
112
+ step1.env.DELETED_BRANCHES != expected_step1.env.DELETED_BRANCHES
113
+ msg := "clean-merged-branch.yml: env.DELETED_BRANCHES має бути як у template (ga.mdc)"
118
114
  }
119
115
 
120
116
  deny contains msg if {
121
117
  not echo_deleted_branches
122
- msg := "clean-merged-branch.yml: run має echo Deleted branches як у ga.mdc"
118
+ msg := "clean-merged-branch.yml: run має echo Deleted branches як у template (ga.mdc)"
123
119
  }
124
120
 
125
121
  # ── helpers ────────────────────────────────────────────────────────────────
@@ -128,15 +124,17 @@ has_expected_cron if {
128
124
  gha_on.schedule[_].cron == expected_cron
129
125
  }
130
126
 
131
- has_workflow_dispatch if {
132
- is_object(gha_on.workflow_dispatch)
133
- }
134
-
135
- ignore_branches_has_main_and_dev if {
136
- contains(step0.with.ignore_branches, "main")
137
- contains(step0.with.ignore_branches, "dev")
127
+ # Кожна гілка з template-літералу (через кому) має бути присутня у actual.
128
+ ignore_branches_subset if {
129
+ required_branches := split(expected_step0.with.ignore_branches, ",")
130
+ actual := step0.with.ignore_branches
131
+ every b in required_branches {
132
+ contains(actual, trim_space(b))
133
+ }
138
134
  }
139
135
 
140
136
  echo_deleted_branches if {
141
- contains(step1.run, expected_echo_substring)
137
+ # Звіряємо substring "echo "Deleted branches: …${DELETED_BRANCHES}…"" — формується з template run.
138
+ contains(step1.run, "Deleted branches:")
139
+ contains(step1.run, "${DELETED_BRANCHES}")
142
140
  }
@@ -0,0 +1,37 @@
1
+ name: Clean abandoned branches
2
+
3
+ on:
4
+ # Run daily at midnight
5
+ schedule:
6
+ - cron: '0 1 15 * *'
7
+
8
+ # Allow workflow to be manually run from the GitHub UI
9
+ workflow_dispatch: {}
10
+
11
+ concurrency:
12
+ group: ${{ github.ref }}-${{ github.workflow }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ cleanup_old_branches:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: write
20
+ steps:
21
+ - id: delete_stuff
22
+ name: Delete those pesky dead branches
23
+ uses: phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3
24
+ with:
25
+ github_token: ${{ github.token }}
26
+ last_commit_age_days: 90
27
+ ignore_branches: main,dev
28
+ # `no` у workflow → Go-yaml парсить як bool false; тут пишемо явно false,
29
+ # бо template читає `yaml` (YAML 1.2), де `no` лишається рядком — а нам
30
+ # потрібна семантична відповідність runtime-парсингу conftest.
31
+ dry_run: false
32
+
33
+ - name: Get output
34
+ env:
35
+ DELETED_BRANCHES: ${{ steps.delete_stuff.outputs.deleted_branches }}
36
+ run: |
37
+ echo "Deleted branches: ${DELETED_BRANCHES}"
@@ -1,42 +1,38 @@
1
- # Порт перевірки `validateGitAiWorkflowStructure` з `npm/scripts/check-ga.mjs` (ga.mdc).
1
+ # Перевірка `.github/workflows/git-ai.yml` (ga.mdc).
2
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).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/git-ai.yml.snippet.yml.
5
+ # Substring-перевірки (`if:`, `run:` блоки) — на основі ключових фраз з template
6
+ # steps, бо повний multi-line `run:` дуже крихкий для exact-match.
10
7
  package ga.git_ai
11
8
 
12
9
  import rego.v1
13
10
 
14
- # ── Очікувані значення ─────────────────────────────────────────────────────
15
-
16
- expected_name := "Git AI"
17
-
18
- expected_if_substring := "github.event.pull_request.merged == true"
19
-
20
- expected_install_substring := "curl -fsSL https://usegitai.com/install.sh | bash"
21
-
22
- expected_run_substring := "git-ai ci github run"
23
-
24
- # ── Аліаси на input ────────────────────────────────────────────────────────
25
- #
26
- # YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
11
+ # ── Аліаси ─────────────────────────────────────────────────────────────────
27
12
 
28
13
  gha_on := input["true"]
29
14
 
30
- # Job-id містить дефіс — звертаємося через `[…]`. Імʼя `job` (без префіксу пакету)
31
- # — щоб уникнути regal-правила `rule-name-repeats-package`.
32
15
  job := input.jobs["git-ai"]
33
16
 
34
- # Усі `run:` зі steps цього job-а, склеєні в один blob — для substring-перевірки.
35
17
  job_run_blob := concat("\n", [run |
36
18
  run := job.steps[_].run
37
19
  ])
38
20
 
39
- # ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
21
+ expected_name := data.template.snippet.name
22
+
23
+ expected_types := {t | some t in data.template.snippet.on.pull_request.types}
24
+
25
+ expected_if := data.template.snippet.jobs["git-ai"].if
26
+
27
+ expected_perms := data.template.snippet.jobs["git-ai"].permissions
28
+
29
+ # Substring-маркери з template `run:` блоків — ключові команди, наявність яких
30
+ # гарантує що workflow робить очікувані дії. Конкретний multi-line — не порівнюємо.
31
+ install_substring := "https://usegitai.com/install.sh"
32
+
33
+ run_substring := "git-ai ci github run"
34
+
35
+ # ── deny rules ─────────────────────────────────────────────────────────────
40
36
 
41
37
  deny contains msg if {
42
38
  input.name != expected_name
@@ -54,30 +50,27 @@ deny contains msg if {
54
50
  }
55
51
 
56
52
  deny contains msg if {
57
- not contains(job_if_str, expected_if_substring)
58
- msg := "git-ai.yml: job має містити if: github.event.pull_request.merged == true (ga.mdc)"
53
+ not contains(job_if_str, expected_if)
54
+ msg := sprintf("git-ai.yml: job має містити if: %s (ga.mdc)", [expected_if])
59
55
  }
60
56
 
61
57
  deny contains msg if {
62
- job.permissions.contents != "write"
63
- msg := "git-ai.yml: permissions мають бути contents: write (ga.mdc)"
58
+ job.permissions.contents != expected_perms.contents
59
+ msg := sprintf("git-ai.yml: permissions.contents має бути %s (ga.mdc)", [expected_perms.contents])
64
60
  }
65
61
 
66
62
  deny contains msg if {
67
- not contains(job_run_blob, expected_install_substring)
63
+ not contains(job_run_blob, install_substring)
68
64
  msg := "git-ai.yml: має встановлювати git-ai через curl | bash (ga.mdc)"
69
65
  }
70
66
 
71
67
  deny contains msg if {
72
- not contains(job_run_blob, expected_run_substring)
73
- msg := "git-ai.yml: має виконувати git-ai ci github run (ga.mdc)"
68
+ not contains(job_run_blob, run_substring)
69
+ msg := sprintf("git-ai.yml: має виконувати %s (ga.mdc)", [run_substring])
74
70
  }
75
71
 
76
72
  # ── helpers ────────────────────────────────────────────────────────────────
77
73
 
78
- # `if` поле job-а може бути відсутнім — тоді `sprintf` дає невизначене значення
79
- # і спрацьовує `default`, повертаючи порожній рядок; `contains(…)` нижче дасть
80
- # false і відповідне `deny`-правило спрацює зі зрозумілим повідомленням.
81
74
  default job_if_str := ""
82
75
 
83
76
  job_if_str := sprintf("%v", [job.if])