@7n/rules 1.4.1 → 1.5.0
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 +12 -0
- package/bin/n-rules.js +65 -17
- package/package.json +11 -1
- package/rules/abie/clean_merged_ignore_branches/concern.json +3 -0
- package/rules/docker/lint_docker_yml/concern.json +3 -0
- package/rules/js/lint_js_yml/concern.json +3 -0
- package/rules/k8s/lint_k8s_yml/concern.json +3 -0
- package/rules/npm-module/npm_publish_yml/concern.json +3 -0
- package/rules/php/lint_php_yml/concern.json +3 -0
- package/rules/python/lint_python_yml/concern.json +3 -0
- package/rules/rust/lint_rust_yml/concern.json +3 -0
- package/rules/rust/toolchain_cache/concern.json +10 -1
- package/rules/security/lint_security_yml/concern.json +3 -0
- package/rules/style/lint_style_yml/concern.json +3 -0
- package/rules/tauri/main.mdc +1 -1
- package/rules/tauri/release/release.mdc +15 -0
- package/rules/text/lint_text/concern.json +3 -0
- package/schemas/concern.json +86 -16
- package/schemas/n-rules.json +11 -1
- package/schemas/v8r-catalog.json +123 -27
- package/scripts/auto-rules.mjs +47 -6
- package/scripts/docs/auto-rules.md +3 -1
- package/scripts/docs/skills-cli.md +3 -2
- package/scripts/docs/upgrade-n-rules-and-install.md +3 -1
- package/scripts/lib/acp-runner.mjs +174 -0
- package/scripts/lib/concern-meta.mjs +8 -1
- package/scripts/lib/docs/acp-runner.md +58 -0
- package/scripts/lib/docs/concern-meta.md +1 -1
- package/scripts/lib/docs/read-n-rules-config-lite.md +3 -1
- package/scripts/lib/docs/resolve-plugins.md +17 -0
- package/scripts/lib/docs/run-conftest-batch.md +3 -1
- package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +3 -1
- package/scripts/lib/lint-surface/docs/run-detectors.md +5 -1
- package/scripts/lib/lint-surface/policy-lint-adapter.mjs +2 -0
- package/scripts/lib/lint-surface/run-detectors.mjs +72 -5
- package/scripts/lib/read-n-rules-config-lite.mjs +5 -3
- package/scripts/lib/resolve-plugins.mjs +248 -0
- package/scripts/lib/run-conftest-batch.mjs +3 -1
- package/scripts/skills-cli.mjs +30 -52
- package/scripts/upgrade-n-rules-and-install.mjs +5 -4
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.mdc +0 -20
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -112
- package/rules/ga/clean_ga_workflows/concern.json +0 -10
- package/rules/ga/clean_ga_workflows/docs/fix-clean_ga_workflows.md +0 -33
- package/rules/ga/clean_ga_workflows/docs/index.md +0 -9
- package/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs +0 -8
- package/rules/ga/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +0 -26
- package/rules/ga/clean_merged_branch/clean_merged_branch.mdc +0 -24
- package/rules/ga/clean_merged_branch/clean_merged_branch.rego +0 -163
- package/rules/ga/clean_merged_branch/concern.json +0 -10
- package/rules/ga/clean_merged_branch/docs/fix-clean_merged_branch.md +0 -27
- package/rules/ga/clean_merged_branch/docs/index.md +0 -9
- package/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs +0 -8
- package/rules/ga/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +0 -37
- package/rules/ga/docs/index.md +0 -11
- package/rules/ga/git_ai/concern.json +0 -11
- package/rules/ga/git_ai/docs/fix-git_ai.md +0 -30
- package/rules/ga/git_ai/docs/index.md +0 -9
- package/rules/ga/git_ai/fix-git_ai.mjs +0 -5
- package/rules/ga/git_ai/git_ai.mdc +0 -19
- package/rules/ga/git_ai/git_ai.rego +0 -76
- package/rules/ga/git_ai/template/git-ai.yml.snippet.yml +0 -30
- package/rules/ga/lint_ga/concern.json +0 -10
- package/rules/ga/lint_ga/docs/fix-lint_ga.md +0 -32
- package/rules/ga/lint_ga/docs/index.md +0 -9
- package/rules/ga/lint_ga/fix-lint_ga.mjs +0 -5
- package/rules/ga/lint_ga/lint_ga.mdc +0 -21
- package/rules/ga/lint_ga/lint_ga.rego +0 -132
- package/rules/ga/lint_ga/template/lint-ga.yml.snippet.yml +0 -41
- package/rules/ga/main.json +0 -1
- package/rules/ga/main.mdc +0 -17
- package/rules/ga/vscode_extensions/concern.json +0 -11
- package/rules/ga/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
- package/rules/ga/vscode_extensions/docs/index.md +0 -11
- package/rules/ga/vscode_extensions/fix-vscode_extensions.mjs +0 -1
- package/rules/ga/vscode_extensions/template/extensions.json.snippet.json +0 -1
- package/rules/ga/vscode_extensions/vscode_extensions.mdc +0 -9
- package/rules/ga/vscode_extensions/vscode_extensions.rego +0 -18
- package/rules/ga/vscode_settings/concern.json +0 -11
- package/rules/ga/vscode_settings/docs/fix-vscode_settings.md +0 -26
- package/rules/ga/vscode_settings/docs/index.md +0 -9
- package/rules/ga/vscode_settings/fix-vscode_settings.mjs +0 -5
- package/rules/ga/vscode_settings/template/settings.json.snippet.json +0 -1
- package/rules/ga/vscode_settings/vscode_settings.mdc +0 -9
- package/rules/ga/vscode_settings/vscode_settings.rego +0 -22
- package/rules/ga/workflow_common/concern.json +0 -9
- package/rules/ga/workflow_common/template/uses-min-versions.snippet.json +0 -4
- package/rules/ga/workflow_common/workflow_common.mdc +0 -18
- package/rules/ga/workflow_common/workflow_common.rego +0 -288
- package/rules/ga/workflows/concern.json +0 -8
- package/rules/ga/workflows/docs/fix-workflows.md +0 -37
- package/rules/ga/workflows/docs/main.md +0 -38
- package/rules/ga/workflows/fix-workflows.mjs +0 -212
- package/rules/ga/workflows/main.mjs +0 -433
- package/rules/ga/workflows/workflows.mdc +0 -33
- package/rules/ga/zizmor_yml/concern.json +0 -11
- package/rules/ga/zizmor_yml/docs/fix-zizmor_yml.md +0 -28
- package/rules/ga/zizmor_yml/docs/index.md +0 -9
- package/rules/ga/zizmor_yml/fix-zizmor_yml.mjs +0 -3
- package/rules/ga/zizmor_yml/template/zizmor.yml.snippet.yml +0 -5
- package/rules/ga/zizmor_yml/zizmor_yml.mdc +0 -9
- package/rules/ga/zizmor_yml/zizmor_yml.rego +0 -27
package/scripts/skills-cli.mjs
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
|
|
5
5
|
* Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
|
|
6
6
|
* `.n-rules.json`) — далі stdout або виконання через один з раннерів: вбудований
|
|
7
|
-
* pi-агент, чи зовнішній
|
|
7
|
+
* pi-агент, чи зовнішній ACP-агент (`cursor-agent acp`, `codex-acp`, або deprecated `claude`).
|
|
8
8
|
*
|
|
9
9
|
* Підтримувані формати:
|
|
10
10
|
* `npx \@7n/rules skill list`
|
|
11
11
|
* `npx \@7n/rules skill taze`
|
|
12
12
|
* `npx \@7n/rules skill pi taze` — виконати через вбудований pi-агент (рекомендовано)
|
|
13
13
|
* `npx \@7n/rules skill pi taze "онови залежності"`
|
|
14
|
-
* `npx \@7n/rules skill cursor taze` —
|
|
15
|
-
* `npx \@7n/rules skill codex taze` —
|
|
16
|
-
* `npx \@7n/rules skill claude taze` — deprecated:
|
|
14
|
+
* `npx \@7n/rules skill cursor taze` — Cursor CLI через ACP (`cursor-agent acp`)
|
|
15
|
+
* `npx \@7n/rules skill codex taze` — Codex через ACP-адаптер (`@agentclientprotocol/codex-acp`)
|
|
16
|
+
* `npx \@7n/rules skill claude taze` — deprecated: Claude Code через ACP-адаптер
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { spawnSync } from 'node:child_process'
|
|
@@ -22,20 +22,17 @@ import { dirname, join } from 'node:path'
|
|
|
22
22
|
import { cwd } from 'node:process'
|
|
23
23
|
import { fileURLToPath } from 'node:url'
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { runAcpRunner } from './lib/acp-runner.mjs'
|
|
26
|
+
import { readSkillMetaRaw, skillTier } from './lib/skill-meta.mjs'
|
|
26
27
|
|
|
27
|
-
/** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`codex`/`claude` — зовнішні
|
|
28
|
+
/** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`codex`/`claude` — зовнішні ACP-агенти (`claude` — deprecated). */
|
|
28
29
|
const RUNNERS = new Set(['pi', 'cursor', 'codex', 'claude'])
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const EXTERNAL_RUNNERS = {
|
|
36
|
-
claude: { bin: 'claude', args: ['-p'], deprecated: true },
|
|
37
|
-
cursor: { bin: 'cursor-agent', args: ['-p'], deprecated: false },
|
|
38
|
-
codex: { bin: 'codex', args: ['exec', '-'], deprecated: false }
|
|
31
|
+
/** Раннери, що йдуть через зовнішнього ACP-агента (`runAcpRunner`), і чи deprecated (друкує попередження перед запуском). */
|
|
32
|
+
const ACP_RUNNERS = {
|
|
33
|
+
claude: { deprecated: true },
|
|
34
|
+
cursor: { deprecated: false },
|
|
35
|
+
codex: { deprecated: false }
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
const USAGE_LINES = [
|
|
@@ -43,8 +40,8 @@ const USAGE_LINES = [
|
|
|
43
40
|
' npx @7n/rules skill list',
|
|
44
41
|
' npx @7n/rules skill <skill-id> ["task"]',
|
|
45
42
|
' npx @7n/rules skill pi <skill-id> ["task"] # вбудований pi-агент (рекомендовано)',
|
|
46
|
-
' npx @7n/rules skill cursor <skill-id> ["task"] #
|
|
47
|
-
' npx @7n/rules skill codex <skill-id> ["task"] #
|
|
43
|
+
' npx @7n/rules skill cursor <skill-id> ["task"] # Cursor CLI через ACP',
|
|
44
|
+
' npx @7n/rules skill codex <skill-id> ["task"] # Codex через ACP-адаптер',
|
|
48
45
|
' npx @7n/rules skill claude <skill-id> ["task"] # deprecated',
|
|
49
46
|
'',
|
|
50
47
|
'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
|
|
@@ -173,44 +170,27 @@ async function runPiRunner(prompt, rawSkillName, skillsRoot, projectDir, logErro
|
|
|
173
170
|
}
|
|
174
171
|
|
|
175
172
|
/**
|
|
176
|
-
* Делегує виконання скіла
|
|
177
|
-
* або deprecated `claude
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* додаємо `--force`, замість того щоб просити користувача розширювати глобальний allowlist
|
|
185
|
-
* у `~/.cursor/cli-config.json`.
|
|
186
|
-
* @param {'claude' | 'cursor' | 'codex'} kind який LLM CLI запускати
|
|
187
|
-
* @param {string} prompt промпт для передачі у stdin
|
|
188
|
-
* @param {string} projectDir робочий каталог дочірнього процесу
|
|
173
|
+
* Делегує виконання скіла зовнішньому ACP-агенту (`cursor-agent acp`, `codex-acp`,
|
|
174
|
+
* або deprecated `claude`-адаптер) через `runAcpRunner` — JSON-RPC поверх stdio,
|
|
175
|
+
* дозволи на tool-calls автоапрувляться (без інтерактивних питань, паритет із
|
|
176
|
+
* колишнім non-interactive `-p`). `claude` лишається як deprecated fallback для
|
|
177
|
+
* тих, у кого pi-модель ще не налаштована; буде прибрано (мігруй на `skill pi`).
|
|
178
|
+
* @param {'claude' | 'cursor' | 'codex'} kind якого ACP-агента запускати
|
|
179
|
+
* @param {string} prompt промпт скіла
|
|
180
|
+
* @param {string} projectDir робочий каталог агента
|
|
189
181
|
* @param {(line: string) => void} logError вивід попередження/помилок
|
|
190
|
-
* @param {
|
|
191
|
-
* @returns {number} exit code
|
|
182
|
+
* @param {{ runAcpRunner?: typeof runAcpRunner }} [deps] інжект для тестів
|
|
183
|
+
* @returns {Promise<number>} exit code (0 — `end_turn`, 1 — інакше)
|
|
192
184
|
*/
|
|
193
|
-
function runLlmCli(kind, prompt, projectDir, logError,
|
|
194
|
-
const runner =
|
|
185
|
+
function runLlmCli(kind, prompt, projectDir, logError, deps = {}) {
|
|
186
|
+
const runner = ACP_RUNNERS[kind]
|
|
187
|
+
const runAcp = deps.runAcpRunner ?? runAcpRunner
|
|
195
188
|
|
|
196
189
|
if (runner.deprecated) {
|
|
197
|
-
logError(`[deprecated] skill ${kind} → use 'skill pi'; зовнішній
|
|
190
|
+
logError(`[deprecated] skill ${kind} → use 'skill pi'; зовнішній ACP-агент буде прибрано`)
|
|
198
191
|
}
|
|
199
192
|
|
|
200
|
-
|
|
201
|
-
throw new Error(`\`${runner.bin}\` not found in PATH. Install ${kind} CLI or use \`skill pi\`.`)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const args = kind === 'cursor' && isolated ? [...runner.args, '--force'] : runner.args
|
|
205
|
-
|
|
206
|
-
const result = spawnSync(runner.bin, args, {
|
|
207
|
-
input: prompt,
|
|
208
|
-
cwd: projectDir,
|
|
209
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
210
|
-
encoding: 'utf8',
|
|
211
|
-
env: process.env
|
|
212
|
-
})
|
|
213
|
-
return result.status ?? 1
|
|
193
|
+
return runAcp(kind, prompt, projectDir, logError, { isBinaryInPath })
|
|
214
194
|
}
|
|
215
195
|
|
|
216
196
|
/**
|
|
@@ -261,9 +241,7 @@ export async function runSkillsCli(argv, options = {}) {
|
|
|
261
241
|
if (first === 'pi') {
|
|
262
242
|
return await runPiRunner(prompt, second, skillsRoot, projectDir, logError, deps)
|
|
263
243
|
}
|
|
264
|
-
|
|
265
|
-
const isolated = skillRequiresRoot(skillMeta)
|
|
266
|
-
return runLlmCli(/** @type {'claude' | 'cursor' | 'codex'} */ (first), prompt, projectDir, logError, isolated)
|
|
244
|
+
return await runLlmCli(/** @type {'claude' | 'cursor' | 'codex'} */ (first), prompt, projectDir, logError, deps)
|
|
267
245
|
}
|
|
268
246
|
|
|
269
247
|
if (skillIds.includes(normalizeSkillId(first))) {
|
|
@@ -101,11 +101,12 @@ async function readPackageVersionSafe(packageRoot) {
|
|
|
101
101
|
/**
|
|
102
102
|
* Шлях до встановленого пакета в `node_modules` або fallback.
|
|
103
103
|
* @param {string} projectRoot корінь репозиторію
|
|
104
|
-
* @param {string} fallbackPackageRoot корінь пакету з поточного процесу
|
|
105
|
-
* @
|
|
104
|
+
* @param {string} fallbackPackageRoot корінь пакету з поточного процесу (`null` для плагінів — fallback-кореня нема)
|
|
105
|
+
* @param {string} [packageName] npm-ім'я пакета (за замовчуванням ядро `@7n/rules`; плагіни передають своє)
|
|
106
|
+
* @returns {string | null} абсолютний шлях до каталогу з `rules/`, `scripts/` тощо, або `fallbackPackageRoot`
|
|
106
107
|
*/
|
|
107
|
-
export function resolveInstalledPackageRoot(projectRoot, fallbackPackageRoot) {
|
|
108
|
-
const installed = join(projectRoot, 'node_modules',
|
|
108
|
+
export function resolveInstalledPackageRoot(projectRoot, fallbackPackageRoot, packageName = PACKAGE_NAME) {
|
|
109
|
+
const installed = join(projectRoot, 'node_modules', packageName)
|
|
109
110
|
if (existsSync(join(installed, 'package.json'))) {
|
|
110
111
|
return installed
|
|
111
112
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
## Структура `clean-ga-workflows.yml` — видалення завершених workflow runs
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `ga.clean_ga_workflows`
|
|
4
|
-
|
|
5
|
-
**Цільовий файл:** `.github/workflows/clean-ga-workflows.yml`
|
|
6
|
-
|
|
7
|
-
### Що перевіряється
|
|
8
|
-
|
|
9
|
-
- `name` — точна відповідність значенню з template
|
|
10
|
-
- `on.schedule[].cron` — має містити cron із template (`0 1 16 * *`)
|
|
11
|
-
- `on.workflow_dispatch` — має бути об'єктом `{}`
|
|
12
|
-
- `jobs.cleanup_old_workflows` — job має існувати
|
|
13
|
-
- `jobs.cleanup_old_workflows.runs-on` — відповідно до template (`ubuntu-latest`)
|
|
14
|
-
- `jobs.cleanup_old_workflows.permissions` — `actions: write`, `contents: read`
|
|
15
|
-
- `steps[0].name` і `steps[0].uses` — відповідно до template (`dmvict/clean-workflow-runs@v1`)
|
|
16
|
-
- `steps[0].with` — `token`, `save_period`, `save_min_runs_number` як у template
|
|
17
|
-
|
|
18
|
-
**Pin-aware `uses`:** SHA-пін того самого action (`owner/action@<40-hex SHA>`, з тег-коментарем `# vX` чи без) задовольняє канонічний тег із template. Якщо в репо вже SHA-пін (zizmor-політика ref-pin) — НЕ замінюй його тегом.
|
|
19
|
-
|
|
20
|
-
Канон: [clean-ga-workflows.yml.snippet.yml](./template/clean-ga-workflows.yml.snippet.yml)
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Перевірка `.github/workflows/clean-ga-workflows.yml` (ga.mdc).
|
|
2
|
-
#
|
|
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.
|
|
7
|
-
package ga.clean_ga_workflows
|
|
8
|
-
|
|
9
|
-
import rego.v1
|
|
10
|
-
|
|
11
|
-
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
12
|
-
# GHA YAML quirk: ключ `on:` — YAML 1.1 boolean `true`, у conftest серіалізується
|
|
13
|
-
# як рядковий ключ "true". Template (через --data JSON) має ключ "on".
|
|
14
|
-
|
|
15
|
-
gha_on := input["true"]
|
|
16
|
-
|
|
17
|
-
step0 := input.jobs.cleanup_old_workflows.steps[0]
|
|
18
|
-
|
|
19
|
-
# Експектації з template.
|
|
20
|
-
expected_name := data.template.snippet.name
|
|
21
|
-
|
|
22
|
-
expected_cron := data.template.snippet.on.schedule[0].cron
|
|
23
|
-
|
|
24
|
-
expected_step0 := data.template.snippet.jobs.cleanup_old_workflows.steps[0]
|
|
25
|
-
|
|
26
|
-
expected_perms := data.template.snippet.jobs.cleanup_old_workflows.permissions
|
|
27
|
-
|
|
28
|
-
expected_runs_on := data.template.snippet.jobs.cleanup_old_workflows["runs-on"]
|
|
29
|
-
|
|
30
|
-
# ── deny rules ─────────────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
deny contains msg if {
|
|
33
|
-
input.name != expected_name
|
|
34
|
-
msg := sprintf("clean-ga-workflows.yml: name має бути %q (ga.mdc)", [expected_name])
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
deny contains msg if {
|
|
38
|
-
not has_expected_cron
|
|
39
|
-
msg := sprintf("clean-ga-workflows.yml: on.schedule має містити cron: '%s' (ga.mdc)", [expected_cron])
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
deny contains msg if {
|
|
43
|
-
not is_object(object.get(gha_on, "workflow_dispatch", null))
|
|
44
|
-
msg := "clean-ga-workflows.yml: має бути workflow_dispatch: {} (ga.mdc)"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
deny contains msg if {
|
|
48
|
-
not input.jobs.cleanup_old_workflows
|
|
49
|
-
msg := "clean-ga-workflows.yml: jobs.cleanup_old_workflows відсутній (ga.mdc)"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
deny contains msg if {
|
|
53
|
-
job := input.jobs.cleanup_old_workflows
|
|
54
|
-
job["runs-on"] != expected_runs_on
|
|
55
|
-
msg := sprintf("clean-ga-workflows.yml: runs-on має бути %s (ga.mdc)", [expected_runs_on])
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
deny contains msg if {
|
|
59
|
-
perms := input.jobs.cleanup_old_workflows.permissions
|
|
60
|
-
not perms_match(perms, expected_perms)
|
|
61
|
-
msg := "clean-ga-workflows.yml: permissions мають бути actions: write, contents: read (ga.mdc)"
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
deny contains msg if {
|
|
65
|
-
step0.name != expected_step0.name
|
|
66
|
-
msg := sprintf("clean-ga-workflows.yml: перший крок має мати name: %s (ga.mdc)", [expected_step0.name])
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
deny contains msg if {
|
|
70
|
-
actual := step0.uses
|
|
71
|
-
not uses_satisfies(actual, expected_step0.uses)
|
|
72
|
-
msg := sprintf("clean-ga-workflows.yml: перший крок має uses: %s (ga.mdc)", [expected_step0.uses])
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
deny contains msg if {
|
|
76
|
-
not step0_with_canonical
|
|
77
|
-
msg := "clean-ga-workflows.yml: with має містити token/save_period/save_min_runs_number як у template (ga.mdc)"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
deny contains msg if {
|
|
81
|
-
step0.with.save_period != expected_step0.with.save_period
|
|
82
|
-
msg := sprintf("clean-ga-workflows.yml: with.save_period має бути %d (ga.mdc)", [expected_step0.with.save_period])
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# ── helpers ────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
# `uses:` з input задовольняє канонічний `owner/action@tag`: точний збіг…
|
|
88
|
-
uses_satisfies(actual, expected) if actual == expected
|
|
89
|
-
|
|
90
|
-
# …або той самий action-slug і ref — повний 40-hex commit SHA (zizmor ref-pin).
|
|
91
|
-
# SHA-пін відповідної версії задовольняє вимогу тега — не даунгрейдимо до тега.
|
|
92
|
-
uses_satisfies(actual, expected) if {
|
|
93
|
-
slug := split(expected, "@")[0]
|
|
94
|
-
startswith(actual, concat("", [slug, "@"]))
|
|
95
|
-
parts := split(actual, "@")
|
|
96
|
-
regex.match(`^[0-9a-fA-F]{40}$`, parts[count(parts) - 1])
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
has_expected_cron if {
|
|
100
|
-
gha_on.schedule[_].cron == expected_cron
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
perms_match(actual, expected) if {
|
|
104
|
-
actual.actions == expected.actions
|
|
105
|
-
actual.contents == expected.contents
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
step0_with_canonical if {
|
|
109
|
-
step0.with.token == expected_step0.with.token
|
|
110
|
-
step0.with.save_period == expected_step0.with.save_period
|
|
111
|
-
step0.with.save_min_runs_number == expected_step0.with.save_min_runs_number
|
|
112
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
-
"policy": {
|
|
4
|
-
"files": {
|
|
5
|
-
"single": ".github/workflows/clean-ga-workflows.yml",
|
|
6
|
-
"required": true
|
|
7
|
-
},
|
|
8
|
-
"missingMessage": ".github/workflows/clean-ga-workflows.yml не існує — створи за каноном ga.mdc"
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-clean_ga_workflows.mjs
|
|
4
|
-
resource: npm/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: e09e62cc
|
|
7
|
-
model: openai-codex/gpt-5.5
|
|
8
|
-
tier: cloud-avg
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.99
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
`patterns` визначає, які шляхи правило свідомо не розглядає як цільові для перевірки: `.github` і `.git` пропускаються, щоб службові каталоги репозиторію не змішувалися з робочим кодом.
|
|
17
|
-
|
|
18
|
-
Правило працює read-only: не записує у файлову систему чи бази даних.
|
|
19
|
-
|
|
20
|
-
## Поведінка
|
|
21
|
-
|
|
22
|
-
1. `patterns` визначає правило автоматичного виправлення для workflow очищення GitHub Actions.
|
|
23
|
-
|
|
24
|
-
2. Правило гарантує наявність очікуваного файлу `.github/workflows/clean-ga-workflows.yml` на основі затвердженого шаблону.
|
|
25
|
-
|
|
26
|
-
3. Виправлення працює як read-only опис дії: саме по собі не записує зміни у файлову систему чи зовнішні сховища.
|
|
27
|
-
|
|
28
|
-
4. Під час застосування загальних перевірок свідомо не розглядаються службові шляхи `.github` і `.git`, щоб не аналізувати інфраструктурні каталоги як звичайний код.
|
|
29
|
-
|
|
30
|
-
## Гарантії поведінки
|
|
31
|
-
|
|
32
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
33
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/ga/clean_ga_workflows
|
|
4
|
-
resource: npm/rules/ga/clean_ga_workflows/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| ------------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-clean_ga_workflows.mjs](fix-clean_ga_workflows.md) | JS Module |
|
|
@@ -1,26 +0,0 @@
|
|
|
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,24 +0,0 @@
|
|
|
1
|
-
## Структура `clean-merged-branch.yml` — видалення злитих гілок
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `ga.clean_merged_branch`
|
|
4
|
-
|
|
5
|
-
**Цільовий файл:** `.github/workflows/clean-merged-branch.yml`
|
|
6
|
-
|
|
7
|
-
### Що перевіряється
|
|
8
|
-
|
|
9
|
-
- `name` — точна відповідність значенню з template
|
|
10
|
-
- `on.schedule[].cron` — має містити cron із template (`0 1 15 * *`)
|
|
11
|
-
- `on.workflow_dispatch` — має бути об'єктом `{}`
|
|
12
|
-
- `jobs.cleanup_old_branches` — job має існувати
|
|
13
|
-
- `jobs.cleanup_old_branches.permissions` — кожне поле із template (`contents: write`, `pull-requests: read`)
|
|
14
|
-
- `steps` — має бути рівно 2 кроки
|
|
15
|
-
- `steps[0].id` — `delete_stuff`; `steps[0].uses` — `phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3`
|
|
16
|
-
- `steps[0].with.github_token`, `last_commit_age_days` — за template
|
|
17
|
-
- `steps[0].with.ignore_branches` — має містити всі гілки з template (`main,dev`)
|
|
18
|
-
- `steps[0].with.dry_run` — `no` (нормалізується: YAML 1.1 `no` → boolean `false` у conftest)
|
|
19
|
-
- `steps[1].name` — за template; `steps[1].env.DELETED_BRANCHES` — за template
|
|
20
|
-
- `steps[1].run` — містить `Deleted branches:` та `${DELETED_BRANCHES}`
|
|
21
|
-
|
|
22
|
-
**Pin-aware `uses`:** SHA-пін того самого action (`owner/action@<40-hex SHA>`, з тег-коментарем `# vX` чи без) задовольняє канонічний тег із template. Якщо в репо вже SHA-пін (zizmor-політика ref-pin) — НЕ замінюй його тегом.
|
|
23
|
-
|
|
24
|
-
Канон: [clean-merged-branch.yml.snippet.yml](./template/clean-merged-branch.yml.snippet.yml)
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
# Перевірка `.github/workflows/clean-merged-branch.yml` (ga.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
-
# Структура --data сформована з template/clean-merged-branch.yml.snippet.yml.
|
|
5
|
-
package ga.clean_merged_branch
|
|
6
|
-
|
|
7
|
-
import rego.v1
|
|
8
|
-
|
|
9
|
-
# ── Аліаси ─────────────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
gha_on := input["true"]
|
|
12
|
-
|
|
13
|
-
steps := input.jobs.cleanup_old_branches.steps
|
|
14
|
-
|
|
15
|
-
step0 := steps[0]
|
|
16
|
-
|
|
17
|
-
step1 := steps[1]
|
|
18
|
-
|
|
19
|
-
expected_name := data.template.snippet.name
|
|
20
|
-
|
|
21
|
-
expected_cron := data.template.snippet.on.schedule[0].cron
|
|
22
|
-
|
|
23
|
-
expected_step0 := data.template.snippet.jobs.cleanup_old_branches.steps[0]
|
|
24
|
-
|
|
25
|
-
expected_step1 := data.template.snippet.jobs.cleanup_old_branches.steps[1]
|
|
26
|
-
|
|
27
|
-
expected_perms := data.template.snippet.jobs.cleanup_old_branches.permissions
|
|
28
|
-
|
|
29
|
-
# ── deny rules ─────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
deny contains msg if {
|
|
32
|
-
input.name != expected_name
|
|
33
|
-
msg := sprintf("clean-merged-branch.yml: name має бути %q (ga.mdc)", [expected_name])
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
deny contains msg if {
|
|
37
|
-
not has_expected_cron
|
|
38
|
-
msg := sprintf("clean-merged-branch.yml: on.schedule має містити cron: '%s' (ga.mdc)", [expected_cron])
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
deny contains msg if {
|
|
42
|
-
not is_object(object.get(gha_on, "workflow_dispatch", null))
|
|
43
|
-
msg := "clean-merged-branch.yml: має бути workflow_dispatch: {} (ga.mdc)"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
deny contains msg if {
|
|
47
|
-
not input.jobs.cleanup_old_branches
|
|
48
|
-
msg := "clean-merged-branch.yml: jobs.cleanup_old_branches відсутній (ga.mdc)"
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
deny contains msg if {
|
|
52
|
-
some permission, expected in expected_perms
|
|
53
|
-
object.get(input.jobs.cleanup_old_branches.permissions, permission, null) != expected
|
|
54
|
-
msg := sprintf("clean-merged-branch.yml: permissions.%s має бути %s (ga.mdc)", [permission, expected])
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
deny contains msg if {
|
|
58
|
-
count(steps) < 2
|
|
59
|
-
msg := "clean-merged-branch.yml: steps має містити 2 кроки як у template (ga.mdc)"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# ── Step 0 (delete_stuff) ──────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
deny contains msg if {
|
|
65
|
-
step0.id != expected_step0.id
|
|
66
|
-
msg := sprintf("clean-merged-branch.yml: перший крок має id: %s (ga.mdc)", [expected_step0.id])
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
deny contains msg if {
|
|
70
|
-
actual := step0.uses
|
|
71
|
-
not uses_satisfies(actual, expected_step0.uses)
|
|
72
|
-
msg := sprintf("clean-merged-branch.yml: перший крок має uses: %s (ga.mdc)", [expected_step0.uses])
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
deny contains msg if {
|
|
76
|
-
step0.with.github_token != expected_step0.with.github_token
|
|
77
|
-
msg := sprintf(
|
|
78
|
-
"clean-merged-branch.yml: with.github_token має бути %s (ga.mdc)",
|
|
79
|
-
[expected_step0.with.github_token],
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
deny contains msg if {
|
|
84
|
-
step0.with.last_commit_age_days != expected_step0.with.last_commit_age_days
|
|
85
|
-
msg := sprintf(
|
|
86
|
-
"clean-merged-branch.yml: with.last_commit_age_days має бути %d (ga.mdc)",
|
|
87
|
-
[expected_step0.with.last_commit_age_days],
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
deny contains msg if {
|
|
92
|
-
not ignore_branches_subset
|
|
93
|
-
msg := sprintf(
|
|
94
|
-
"clean-merged-branch.yml: with.ignore_branches має містити %s (ga.mdc)",
|
|
95
|
-
[expected_step0.with.ignore_branches],
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# YAML 1.1 quirk: `dry_run: no` парситься як boolean false у Go-yaml (conftest).
|
|
100
|
-
# Template (від `yaml` npm) читає `no` як рядок, тому нормалізуємо обидві форми.
|
|
101
|
-
deny contains msg if {
|
|
102
|
-
normalize_dry_run(step0.with.dry_run) != normalize_dry_run(expected_step0.with.dry_run) # noqa: rules-style-no-equality-with-false
|
|
103
|
-
msg := "clean-merged-branch.yml: with.dry_run має бути no (ga.mdc)"
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
# ── Step 1 (Get output) ────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
deny contains msg if {
|
|
109
|
-
step1.name != expected_step1.name
|
|
110
|
-
msg := sprintf("clean-merged-branch.yml: другий крок має name: %s (ga.mdc)", [expected_step1.name])
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
deny contains msg if {
|
|
114
|
-
step1.env.DELETED_BRANCHES != expected_step1.env.DELETED_BRANCHES
|
|
115
|
-
msg := "clean-merged-branch.yml: env.DELETED_BRANCHES має бути як у template (ga.mdc)"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
deny contains msg if {
|
|
119
|
-
not echo_deleted_branches
|
|
120
|
-
msg := "clean-merged-branch.yml: run має echo Deleted branches як у template (ga.mdc)"
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
# ── helpers ────────────────────────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
# `uses:` з input задовольняє канонічний `owner/action@tag`: точний збіг…
|
|
126
|
-
uses_satisfies(actual, expected) if actual == expected
|
|
127
|
-
|
|
128
|
-
# …або той самий action-slug і ref — повний 40-hex commit SHA (zizmor ref-pin).
|
|
129
|
-
# SHA-пін відповідної версії задовольняє вимогу тега — не даунгрейдимо до тега.
|
|
130
|
-
uses_satisfies(actual, expected) if {
|
|
131
|
-
slug := split(expected, "@")[0]
|
|
132
|
-
startswith(actual, concat("", [slug, "@"]))
|
|
133
|
-
parts := split(actual, "@")
|
|
134
|
-
regex.match(`^[0-9a-fA-F]{40}$`, parts[count(parts) - 1])
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
has_expected_cron if {
|
|
138
|
-
gha_on.schedule[_].cron == expected_cron
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# Кожна гілка з template-літералу (через кому) має бути присутня у actual.
|
|
142
|
-
ignore_branches_subset if {
|
|
143
|
-
required_branches := split(expected_step0.with.ignore_branches, ",")
|
|
144
|
-
actual := step0.with.ignore_branches
|
|
145
|
-
every b in required_branches {
|
|
146
|
-
contains(actual, trim_space(b))
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
echo_deleted_branches if {
|
|
151
|
-
# Звіряємо substring "echo "Deleted branches: …${DELETED_BRANCHES}…"" — формується з template run.
|
|
152
|
-
contains(step1.run, "Deleted branches:")
|
|
153
|
-
contains(step1.run, "${DELETED_BRANCHES}")
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
normalize_dry_run(false) := false
|
|
157
|
-
|
|
158
|
-
normalize_dry_run(value) := false if lower(sprintf("%v", [value])) == "no"
|
|
159
|
-
|
|
160
|
-
normalize_dry_run(value) := value if {
|
|
161
|
-
value != false # noqa: equals-pattern-matching
|
|
162
|
-
lower(sprintf("%v", [value])) != "no"
|
|
163
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
-
"policy": {
|
|
4
|
-
"files": {
|
|
5
|
-
"single": ".github/workflows/clean-merged-branch.yml",
|
|
6
|
-
"required": true
|
|
7
|
-
},
|
|
8
|
-
"missingMessage": ".github/workflows/clean-merged-branch.yml не існує — створи за каноном ga.mdc"
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-clean_merged_branch.mjs
|
|
4
|
-
resource: npm/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: c38d9d28
|
|
7
|
-
model: openai-codex/gpt-5.4-mini
|
|
8
|
-
score: 100
|
|
9
|
-
issues: judge:inaccurate:0.98
|
|
10
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Огляд
|
|
14
|
-
|
|
15
|
-
`patterns` звіряє шаблонну конфігурацію з відповідними файлами репозиторію, щоб підтримувати узгодженість між шаблоном і робочим станом. Працює лише на читання, не торкається `.github` і `.git`, і не змінює файлову систему чи базу даних.
|
|
16
|
-
|
|
17
|
-
## Поведінка
|
|
18
|
-
|
|
19
|
-
1. `patterns` формує набір правил для узгодження шаблонної конфігурації з цільовим workflow-файлом `.github/workflows/clean-merged-branch.yml`.
|
|
20
|
-
2. `patterns` використовує це правило як частину автоматизованого вирівнювання між шаблоном і робочим репозиторієм, щоб підтримувати однакову поведінку в потрібному workflow.
|
|
21
|
-
3. `patterns` свідомо не охоплює шляхи `.github` і `.git`, щоб не зачіпати службові та внутрішні дані репозиторію.
|
|
22
|
-
4. `patterns` працює read-only: лише описує, що треба зіставити, і не записує зміни у файлову систему чи базу даних.
|
|
23
|
-
|
|
24
|
-
## Гарантії поведінки
|
|
25
|
-
|
|
26
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
27
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/ga/clean_merged_branch
|
|
4
|
-
resource: npm/rules/ga/clean_merged_branch/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| --------------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-clean_merged_branch.mjs](fix-clean_merged_branch.md) | JS Module |
|