@7n/rules 1.5.1 → 1.7.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 +83 -21
- package/package.json +1 -2
- package/rules/abie/main.mdc +0 -6
- package/rules/js/check/docs/main.md +1 -1
- package/rules/js/check/main.mjs +6 -10
- package/rules/js/main.mdc +0 -1
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +4 -1
- package/rules/npm-module/main.mdc +0 -2
- package/rules/php/tooling/docs/main.md +1 -1
- package/rules/php/tooling/main.mjs +2 -7
- package/rules/python/tooling/docs/main.md +1 -1
- package/rules/python/tooling/main.mjs +2 -7
- package/rules/rego/lib/docs/run-external-tool.md +1 -2
- package/rules/rego/lib/run-external-tool.mjs +4 -1
- package/rules/security/main.mdc +0 -1
- package/rules/style/main.mdc +0 -1
- package/rules/style/tooling/docs/main.md +1 -1
- package/rules/style/tooling/main.mjs +2 -7
- package/rules/tauri/release/release.mdc +1 -1
- package/rules/text/formatting/docs/main.md +1 -1
- package/rules/text/formatting/main.mjs +11 -11
- package/rules/text/main.mdc +0 -1
- package/scripts/docs/skills-cli.md +1 -1
- package/scripts/lib/acp-runner.mjs +26 -33
- package/scripts/lib/docs/acp-runner.md +5 -5
- package/scripts/lib/docs/mirror-parity.md +1 -1
- package/scripts/lib/mirror-parity.mjs +49 -16
- package/scripts/skills-cli.mjs +35 -26
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +0 -19
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +0 -81
- package/rules/abie/clean_merged_ignore_branches/concern.json +0 -11
- package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +0 -31
- package/rules/abie/clean_merged_ignore_branches/docs/index.md +0 -9
- package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +0 -8
- package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +0 -9
- package/rules/docker/lint_docker_yml/concern.json +0 -12
- package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +0 -31
- package/rules/docker/lint_docker_yml/docs/index.md +0 -9
- package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +0 -5
- package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +0 -14
- package/rules/docker/lint_docker_yml/lint_docker_yml.rego +0 -79
- package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +0 -41
- package/rules/js/lint_js_yml/concern.json +0 -12
- package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +0 -28
- package/rules/js/lint_js_yml/docs/index.md +0 -9
- package/rules/js/lint_js_yml/fix-lint_js_yml.mjs +0 -5
- package/rules/js/lint_js_yml/lint_js_yml.mdc +0 -14
- package/rules/js/lint_js_yml/lint_js_yml.rego +0 -117
- package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +0 -40
- package/rules/k8s/lint_k8s_yml/concern.json +0 -12
- package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +0 -30
- package/rules/k8s/lint_k8s_yml/docs/index.md +0 -9
- package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +0 -5
- package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +0 -57
- package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +0 -43
- package/rules/npm-module/npm_publish_yml/concern.json +0 -13
- package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +0 -28
- package/rules/npm-module/npm_publish_yml/docs/index.md +0 -9
- package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +0 -8
- package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +0 -49
- package/rules/php/lint_php_yml/concern.json +0 -12
- package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +0 -28
- package/rules/php/lint_php_yml/docs/index.md +0 -9
- package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +0 -5
- package/rules/php/lint_php_yml/lint_php_yml.mdc +0 -23
- package/rules/php/lint_php_yml/lint_php_yml.rego +0 -34
- package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +0 -47
- package/rules/python/lint_python_yml/concern.json +0 -12
- package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +0 -27
- package/rules/python/lint_python_yml/docs/index.md +0 -9
- package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +0 -5
- package/rules/python/lint_python_yml/lint_python_yml.mdc +0 -12
- package/rules/python/lint_python_yml/lint_python_yml.rego +0 -68
- package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +0 -41
- package/rules/rust/lint_rust_yml/concern.json +0 -14
- package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +0 -28
- package/rules/rust/lint_rust_yml/docs/index.md +0 -9
- package/rules/rust/lint_rust_yml/fix-lint_rust_yml.mjs +0 -5
- package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +0 -17
- package/rules/rust/lint_rust_yml/lint_rust_yml.rego +0 -79
- package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +0 -44
- package/rules/rust/toolchain_cache/concern.json +0 -14
- package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +0 -35
- package/rules/rust/toolchain_cache/docs/index.md +0 -12
- package/rules/rust/toolchain_cache/docs/main.md +0 -57
- package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +0 -117
- package/rules/rust/toolchain_cache/main.mjs +0 -177
- package/rules/rust/toolchain_cache/toolchain_cache.mdc +0 -23
- package/rules/security/lint_security_yml/concern.json +0 -14
- package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +0 -31
- package/rules/security/lint_security_yml/docs/index.md +0 -9
- package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +0 -8
- package/rules/security/lint_security_yml/lint_security_yml.mdc +0 -7
- package/rules/security/lint_security_yml/lint_security_yml.rego +0 -33
- package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +0 -31
- package/rules/style/lint_style_yml/concern.json +0 -12
- package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +0 -31
- package/rules/style/lint_style_yml/docs/index.md +0 -9
- package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +0 -5
- package/rules/style/lint_style_yml/lint_style_yml.mdc +0 -13
- package/rules/style/lint_style_yml/lint_style_yml.rego +0 -38
- package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +0 -39
- package/rules/text/lint_text/concern.json +0 -11
- package/rules/text/lint_text/docs/fix-lint_text.md +0 -28
- package/rules/text/lint_text/docs/index.md +0 -9
- package/rules/text/lint_text/fix-lint_text.mjs +0 -5
- package/rules/text/lint_text/lint_text.mdc +0 -19
- package/rules/text/lint_text/lint_text.rego +0 -104
- package/rules/text/lint_text/template/lint-text.yml.snippet.yml +0 -61
|
@@ -12,7 +12,7 @@ agentclientprotocol.com) — JSON-RPC поверх stdio замість сиро
|
|
|
12
12
|
для раннерів `cursor`/`codex`/`claude` у `skills-cli.mjs`.
|
|
13
13
|
|
|
14
14
|
`cursor` запускається через нативний ACP-режим `cursor-agent acp` (зовнішній
|
|
15
|
-
бінарник у PATH). `codex`/`claude` — через офіційні
|
|
15
|
+
бінарник у PATH). `codex`/`claude` — через офіційні вбудовані адаптери
|
|
16
16
|
(`@agentclientprotocol/codex-acp`, `@agentclientprotocol/claude-agent-acp`), що
|
|
17
17
|
самі керують своїм рушієм — зовнішній бінарник `codex`/`claude` у PATH більше
|
|
18
18
|
не потрібен.
|
|
@@ -20,7 +20,7 @@ agentclientprotocol.com) — JSON-RPC поверх stdio замість сиро
|
|
|
20
20
|
## Поведінка
|
|
21
21
|
|
|
22
22
|
- `ACP_AGENT_COMMANDS` — команда запуску агента на провайдер: `cursor` — бінарник
|
|
23
|
-
у PATH; `codex`/`claude` — резолвляться з `bin`-запису
|
|
23
|
+
у PATH; `codex`/`claude` — резолвляться з `bin`-запису вбудованого пакета-адаптера.
|
|
24
24
|
- `stopReasonToExitCode` — мапить ACP `StopReason` прогону на exit code: `end_turn` → `0`,
|
|
25
25
|
усе інше (`max_tokens`, `max_turn_requests`, `refusal`, `cancelled`) → `1`.
|
|
26
26
|
- `resolveAdapterBin` — резолвить абсолютний шлях до bin-файлу адаптера з його `package.json`.
|
|
@@ -29,7 +29,7 @@ agentclientprotocol.com) — JSON-RPC поверх stdio замість сиро
|
|
|
29
29
|
режимом — скіл є явною user-invocation, тож дозволи на tool-calls не питаються
|
|
30
30
|
інтерактивно.
|
|
31
31
|
- `AcpSkillClient` (internal) — ACP `Client` для скіл-раннера: `requestPermission`
|
|
32
|
-
|
|
32
|
+
автоматично схвалює через `pickAutoPermissionOptionId`, `sessionUpdate` стрімить текстові
|
|
33
33
|
дельти (`agent_message_chunk`) у переданий `out`, `readTextFile`/`writeTextFile`
|
|
34
34
|
реалізовані напряму через `node:fs` (full-trust режим, без write-guard).
|
|
35
35
|
- `runAcpRunner` — підʼєднується до агента (`initialize` → `newSession` → `prompt`),
|
|
@@ -41,14 +41,14 @@ agentclientprotocol.com) — JSON-RPC поверх stdio замість сиро
|
|
|
41
41
|
- `ACP_AGENT_COMMANDS` — таблиця команд запуску на провайдер.
|
|
42
42
|
- `stopReasonToExitCode(stopReason)` — `StopReason` → `0 | 1`.
|
|
43
43
|
- `resolveAdapterBin(adapterPackage)` — шлях до bin-файлу адаптера.
|
|
44
|
-
- `pickAutoPermissionOptionId(options)` — `optionId`
|
|
44
|
+
- `pickAutoPermissionOptionId(options)` — `optionId` автоматично обраного варіанту дозволу.
|
|
45
45
|
- `runAcpRunner(kind, prompt, projectDir, logError, deps?)` — виконує скіл через
|
|
46
46
|
ACP-агента `kind` (`cursor`|`codex`|`claude`); `deps` — інжекти для тестів
|
|
47
47
|
(`acp`, `spawnFn`, `out`, `resolveAdapterBin`, `isBinaryInPath`).
|
|
48
48
|
|
|
49
49
|
## Гарантії поведінки
|
|
50
50
|
|
|
51
|
-
- Permission
|
|
51
|
+
- Permission-запити від агента автоматично схвалюються без інтерактивного запиту —
|
|
52
52
|
парність із колишнім non-interactive `-p`/`exec -` режимом (full user-trust,
|
|
53
53
|
без write-guard, як і в `@7n/llm-lib/agent-skill`).
|
|
54
54
|
- `cursor` кидає, якщо `cursor-agent` відсутній у PATH (перевіряється до спавну
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parity дзеркала правил: `.cursor/rules/n-<id>.mdc` має дорівнювати канонічному
|
|
3
|
-
* `
|
|
4
|
-
* застосовує синк (`readBundledRuleContent` → `inlineTemplateLinks`).
|
|
5
|
-
* коли канонічний `.mdc` змінюють, не регенерувавши
|
|
3
|
+
* `main.mdc` правила з inlined-шаблонами — тим самим трансформом, що його
|
|
4
|
+
* застосовує синк (`readBundledRuleContent` → `inlineTemplateLinks` → mixin-extras).
|
|
5
|
+
* Дрейф виникає, коли канонічний `.mdc` змінюють, не регенерувавши дзеркало.
|
|
6
|
+
*
|
|
7
|
+
* Multi-dir: власник правила шукається у `npm/rules/<id>` і `plugins/<p>/rules/<id>`
|
|
8
|
+
* (перший з `main.mdc`); решта тек `rules/<id>` інших джерел — mixin-extras, їхні
|
|
9
|
+
* concern-mdc доінлайнюються після концернів власника (як у синку).
|
|
6
10
|
*
|
|
7
11
|
* Використовується і тестом-гардом (drift === []), і разовою регенерацією.
|
|
8
12
|
*/
|
|
@@ -10,41 +14,70 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
|
10
14
|
import { dirname, join } from 'node:path'
|
|
11
15
|
|
|
12
16
|
import { appendDiscoveredMdcFiles, inlineTemplateLinks } from './inline-template-links.mjs'
|
|
17
|
+
import { readNRulesConfigLite } from './read-n-rules-config-lite.mjs'
|
|
18
|
+
import { resolvePlugins } from './resolve-plugins.mjs'
|
|
13
19
|
|
|
14
20
|
const MIRROR_PREFIX = 'n-'
|
|
15
21
|
const MDC_EXT = '.mdc'
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
24
|
+
* Rules-джерела репо у порядку пріоритету: ядро, потім АКТИВНІ плагіни (з `.n-rules.json`,
|
|
25
|
+
* резолв через node_modules — той самий шлях, що у синку; неактивні plugins/* монорепо
|
|
26
|
+
* не враховуються, бо їх нема і в дзеркалах).
|
|
27
|
+
* @param {string} repoRoot корінь репо
|
|
28
|
+
* @returns {Promise<string[]>} абсолютні шляхи rules-каталогів
|
|
29
|
+
*/
|
|
30
|
+
async function repoRulesDirs(repoRoot) {
|
|
31
|
+
const dirs = [join(repoRoot, 'npm/rules')]
|
|
32
|
+
const config = await readNRulesConfigLite(repoRoot)
|
|
33
|
+
for (const plugin of resolvePlugins(repoRoot, { plugins: config.plugins }, { allowInstall: false, quiet: true })) {
|
|
34
|
+
dirs.push(plugin.rulesDir)
|
|
35
|
+
}
|
|
36
|
+
return dirs
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Керовані дзеркала `.cursor/rules/n-<id>.mdc` з канонічним джерелом у ядрі або плагіні.
|
|
41
|
+
* Дзеркала без канону (зовнішні) пропускаються.
|
|
20
42
|
* @param {string} repoRoot корінь репо
|
|
21
|
-
* @returns {{ id: string, mirrorPath: string, canonicalPath: string }[]} список
|
|
43
|
+
* @returns {Promise<{ id: string, mirrorPath: string, canonicalPath: string, extraDirs: string[] }[]>} список
|
|
22
44
|
*/
|
|
23
|
-
export function listManagedMirrors(repoRoot) {
|
|
45
|
+
export async function listManagedMirrors(repoRoot) {
|
|
24
46
|
const rulesDir = join(repoRoot, '.cursor/rules')
|
|
25
47
|
if (!existsSync(rulesDir)) return []
|
|
48
|
+
const sources = await repoRulesDirs(repoRoot)
|
|
26
49
|
return readdirSync(rulesDir)
|
|
27
50
|
.filter(f => f.startsWith(MIRROR_PREFIX) && f.endsWith(MDC_EXT))
|
|
28
51
|
.map(f => {
|
|
29
52
|
const id = f.slice(MIRROR_PREFIX.length, -MDC_EXT.length)
|
|
53
|
+
const candidates = sources.map(s => join(s, id))
|
|
54
|
+
const owner = candidates.find(dir => existsSync(join(dir, `main${MDC_EXT}`)))
|
|
55
|
+
const extraDirs = owner === undefined ? [] : candidates.filter(dir => dir !== owner && existsSync(dir))
|
|
30
56
|
return {
|
|
31
57
|
id,
|
|
32
58
|
mirrorPath: join(rulesDir, f),
|
|
33
|
-
canonicalPath:
|
|
59
|
+
canonicalPath: owner === undefined ? '' : join(owner, `main${MDC_EXT}`),
|
|
60
|
+
extraDirs
|
|
34
61
|
}
|
|
35
62
|
})
|
|
36
|
-
.filter(m =>
|
|
63
|
+
.filter(m => m.canonicalPath !== '')
|
|
37
64
|
}
|
|
38
65
|
|
|
39
66
|
/**
|
|
40
|
-
* Очікуваний вміст дзеркала = канон з inlined-шаблонами
|
|
41
|
-
*
|
|
67
|
+
* Очікуваний вміст дзеркала = канон з inlined-шаблонами + concern-mdc mixin-джерел
|
|
68
|
+
* (той самий трансформ, що у синку).
|
|
69
|
+
* @param {string} canonicalPath абсолютний шлях `rules/<id>/main.mdc` власника
|
|
70
|
+
* @param {string[]} [extraDirs] теки `rules/<id>` mixin-джерел
|
|
42
71
|
* @returns {Promise<string>} очікуваний текст дзеркала
|
|
43
72
|
*/
|
|
44
|
-
export async function expectedMirrorContent(canonicalPath) {
|
|
73
|
+
export async function expectedMirrorContent(canonicalPath, extraDirs = []) {
|
|
45
74
|
const dir = dirname(canonicalPath)
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
let out = await inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dir)
|
|
76
|
+
out = await appendDiscoveredMdcFiles(out, dir)
|
|
77
|
+
for (const extra of extraDirs) {
|
|
78
|
+
out = await appendDiscoveredMdcFiles(out, extra)
|
|
79
|
+
}
|
|
80
|
+
return out
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
/**
|
|
@@ -54,8 +87,8 @@ export async function expectedMirrorContent(canonicalPath) {
|
|
|
54
87
|
*/
|
|
55
88
|
export async function findMirrorDrift(repoRoot) {
|
|
56
89
|
const drift = []
|
|
57
|
-
for (const m of listManagedMirrors(repoRoot)) {
|
|
58
|
-
const expected = await expectedMirrorContent(m.canonicalPath)
|
|
90
|
+
for (const m of await listManagedMirrors(repoRoot)) {
|
|
91
|
+
const expected = await expectedMirrorContent(m.canonicalPath, m.extraDirs)
|
|
59
92
|
if (readFileSync(m.mirrorPath, 'utf8') !== expected) drift.push(m.id)
|
|
60
93
|
}
|
|
61
94
|
return drift.toSorted()
|
package/scripts/skills-cli.mjs
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
|
|
5
5
|
* Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
|
|
6
6
|
* `.n-rules.json`) — далі stdout або виконання через один з раннерів: вбудований
|
|
7
|
-
* pi-агент, чи зовнішній ACP
|
|
7
|
+
* pi-агент, чи зовнішній ACP-агент. `cursor`/`codex` — через `@7n/llm-lib/acp`
|
|
8
|
+
* (napi-міст до `llm_cascade::acp`, без власного JSON-RPC у JS); deprecated
|
|
9
|
+
* `claude` — окремий JS-шим (`./lib/acp-runner.mjs`), бо Rust-крейт його не моделює.
|
|
8
10
|
*
|
|
9
11
|
* Підтримувані формати:
|
|
10
12
|
* `npx \@7n/rules skill list`
|
|
@@ -12,14 +14,14 @@
|
|
|
12
14
|
* `npx \@7n/rules skill pi taze` — виконати через вбудований pi-агент (рекомендовано)
|
|
13
15
|
* `npx \@7n/rules skill pi taze "онови залежності"`
|
|
14
16
|
* `npx \@7n/rules skill cursor taze` — Cursor CLI через ACP (`cursor-agent acp`)
|
|
15
|
-
* `npx \@7n/rules skill codex taze` — Codex через ACP
|
|
17
|
+
* `npx \@7n/rules skill codex taze` — Codex через ACP (napi-міст `@7n/llm-lib/acp`, Rust спавнить `npx \@agentclientprotocol/codex-acp`)
|
|
16
18
|
* `npx \@7n/rules skill claude taze` — deprecated: Claude Code через ACP-адаптер
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
import {
|
|
21
|
+
import { runAcpAgent } from '@7n/llm-lib/acp'
|
|
20
22
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
21
23
|
import { dirname, join } from 'node:path'
|
|
22
|
-
import { cwd } from 'node:process'
|
|
24
|
+
import { cwd, stdout } from 'node:process'
|
|
23
25
|
import { fileURLToPath } from 'node:url'
|
|
24
26
|
|
|
25
27
|
import { runAcpRunner } from './lib/acp-runner.mjs'
|
|
@@ -28,7 +30,11 @@ import { readSkillMetaRaw, skillTier } from './lib/skill-meta.mjs'
|
|
|
28
30
|
/** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`codex`/`claude` — зовнішні ACP-агенти (`claude` — deprecated). */
|
|
29
31
|
const RUNNERS = new Set(['pi', 'cursor', 'codex', 'claude'])
|
|
30
32
|
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Раннери, що йдуть через зовнішнього ACP-агента, і чи deprecated (друкує попередження
|
|
35
|
+
* перед запуском). `cursor`/`codex` — napi-міст `@7n/llm-lib/acp`; `claude` — окремий
|
|
36
|
+
* JS-шим `runAcpRunner` (Rust-крейт `llm_cascade::acp` `claude` не моделює).
|
|
37
|
+
*/
|
|
32
38
|
const ACP_RUNNERS = {
|
|
33
39
|
claude: { deprecated: true },
|
|
34
40
|
cursor: { deprecated: false },
|
|
@@ -47,17 +53,6 @@ const USAGE_LINES = [
|
|
|
47
53
|
'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
|
|
48
54
|
]
|
|
49
55
|
|
|
50
|
-
/**
|
|
51
|
-
* @param {string} name ім'я бінарника
|
|
52
|
-
* @returns {boolean} чи знайдено бінарник у PATH
|
|
53
|
-
*/
|
|
54
|
-
function isBinaryInPath(name) {
|
|
55
|
-
// Явний `env: process.env` — Bun без нього дає дітям snapshot оточення зі старту процесу
|
|
56
|
-
// і не бачить runtime-змін `process.env.PATH` (та сама причина, що в resolve-cmd.mjs).
|
|
57
|
-
const probe = spawnSync('command', ['-v', name], { shell: true, encoding: 'utf8', env: process.env })
|
|
58
|
-
return probe.status === 0
|
|
59
|
-
}
|
|
60
|
-
|
|
61
56
|
/**
|
|
62
57
|
* @param {string} name ім'я скілу з CLI або каталогу `.cursor/skills/n-*`
|
|
63
58
|
* @returns {string} id каталогу в `npm/skills/<id>/`
|
|
@@ -170,27 +165,41 @@ async function runPiRunner(prompt, rawSkillName, skillsRoot, projectDir, logErro
|
|
|
170
165
|
}
|
|
171
166
|
|
|
172
167
|
/**
|
|
173
|
-
* Делегує виконання скіла зовнішньому ACP
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
168
|
+
* Делегує виконання скіла зовнішньому ACP-агенту. `cursor`/`codex` — через
|
|
169
|
+
* `@7n/llm-lib/acp` (napi-міст до `llm_cascade::acp`: спавн, `session/prompt`,
|
|
170
|
+
* автоапрув дозволів — усе в Rust, без JSON-RPC у JS). `claude` — deprecated
|
|
171
|
+
* JS-шим `runAcpRunner` (Rust його не моделює); буде прибрано (мігруй на `skill pi`).
|
|
172
|
+
* На відміну від колишнього стрімінгу по чанках, napi-шлях повертає повний текст
|
|
173
|
+
* лише по завершенню ходу — Rust-функція не стрімить проміжні токени в JS.
|
|
178
174
|
* @param {'claude' | 'cursor' | 'codex'} kind якого ACP-агента запускати
|
|
179
175
|
* @param {string} prompt промпт скіла
|
|
180
176
|
* @param {string} projectDir робочий каталог агента
|
|
181
177
|
* @param {(line: string) => void} logError вивід попередження/помилок
|
|
182
|
-
* @param {{ runAcpRunner?: typeof runAcpRunner }} [deps]
|
|
183
|
-
* @returns {Promise<number>} exit code (0 —
|
|
178
|
+
* @param {{ runAcpRunner?: typeof runAcpRunner, runAcpAgent?: typeof runAcpAgent, out?: (chunk: string) => void }} [deps] інжекти для тестів
|
|
179
|
+
* @returns {Promise<number>} exit code (0 — успіх, 1 — інакше)
|
|
184
180
|
*/
|
|
185
|
-
function runLlmCli(kind, prompt, projectDir, logError, deps = {}) {
|
|
181
|
+
async function runLlmCli(kind, prompt, projectDir, logError, deps = {}) {
|
|
186
182
|
const runner = ACP_RUNNERS[kind]
|
|
187
|
-
const runAcp = deps.runAcpRunner ?? runAcpRunner
|
|
188
183
|
|
|
189
184
|
if (runner.deprecated) {
|
|
190
185
|
logError(`[deprecated] skill ${kind} → use 'skill pi'; зовнішній ACP-агент буде прибрано`)
|
|
191
186
|
}
|
|
192
187
|
|
|
193
|
-
|
|
188
|
+
if (kind === 'claude') {
|
|
189
|
+
const runAcp = deps.runAcpRunner ?? runAcpRunner
|
|
190
|
+
return runAcp(kind, prompt, projectDir, logError)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const runNativeAcp = deps.runAcpAgent ?? runAcpAgent
|
|
194
|
+
const out = deps.out ?? (chunk => stdout.write(chunk))
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
out(await runNativeAcp(kind, prompt, projectDir))
|
|
198
|
+
return 0
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logError(error instanceof Error ? error.message : String(error))
|
|
201
|
+
return 1
|
|
202
|
+
}
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
/**
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
## Rego: `ignore_branches` у `clean-merged-branch.yml`
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `abie.clean_merged_ignore_branches`
|
|
4
|
-
|
|
5
|
-
Цільовий файл: `.github/workflows/clean-merged-branch.yml`.
|
|
6
|
-
|
|
7
|
-
Перевіряє, що у workflow-файлі є крок з `uses: phpdocker-io/github-actions-delete-abandoned-branches` і що його `with.ignore_branches` містить обидва токени `dev` та `ua` (case-insensitive, пробіли навколо токенів обрізаються). Токени зчитуються з template-файлу `data.template.snippet` — зміна канону автоматично змінює вимоги.
|
|
8
|
-
|
|
9
|
-
Канонічний snippet: [clean-merged-branch.yml.snippet.yml](./template/clean-merged-branch.yml.snippet.yml)
|
|
10
|
-
|
|
11
|
-
```yaml title=".github/workflows/clean-merged-branch.yml (фрагмент)"
|
|
12
|
-
- uses: phpdocker-io/github-actions-delete-abandoned-branches@v2
|
|
13
|
-
with:
|
|
14
|
-
ignore_branches: "dev,ua,main" # ✓ містить dev та ua
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```yaml
|
|
18
|
-
ignore_branches: "main" # ✗ не вистачає dev,ua
|
|
19
|
-
```
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Перевірка `clean-merged-branch.yml` для abie-проєктів (abie.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
-
# Структура --data сформована з template/clean-merged-branch.yml.snippet.yml.
|
|
5
|
-
# Action-маркер (`uses:` substring) і required branches (parsed з template's
|
|
6
|
-
# `ignore_branches`) читаються з template. ga.clean_merged_branch перевіряє
|
|
7
|
-
# повний канон workflow окремо; цей пакет — лише abie-specific шар.
|
|
8
|
-
package abie.clean_merged_ignore_branches
|
|
9
|
-
|
|
10
|
-
import rego.v1
|
|
11
|
-
|
|
12
|
-
# Експектації з template's step (першого з steps).
|
|
13
|
-
expected_step := step if some step in data.template.snippet.jobs.cleanup_old_branches.steps
|
|
14
|
-
|
|
15
|
-
target_action_marker := expected_step.uses
|
|
16
|
-
|
|
17
|
-
required_branches := parsed_ignore_tokens(expected_step.with.ignore_branches)
|
|
18
|
-
|
|
19
|
-
step_missing_msg := sprintf(
|
|
20
|
-
"clean-merged-branch.yml: не знайдено крок з uses: %s (abie.mdc)",
|
|
21
|
-
[target_action_marker],
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
ignore_branches_missing_msg := sprintf(
|
|
25
|
-
"clean-merged-branch.yml: не знайдено with.ignore_branches у кроці %s (abie.mdc)",
|
|
26
|
-
[target_action_marker],
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
# ── deny ─────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
deny contains step_missing_msg if {
|
|
32
|
-
count(target_steps) == 0
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
deny contains ignore_branches_missing_msg if {
|
|
36
|
-
count(target_steps) > 0
|
|
37
|
-
not has_ignore_branches_value
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
deny contains msg if {
|
|
41
|
-
count(target_steps) > 0
|
|
42
|
-
ignore_branches_value != ""
|
|
43
|
-
missing := required_branches - parsed_ignore_tokens(ignore_branches_value)
|
|
44
|
-
count(missing) > 0
|
|
45
|
-
msg := sprintf(
|
|
46
|
-
"clean-merged-branch.yml: ignore_branches має містити %v (зараз: %q; не вистачає: %v) (abie.mdc)",
|
|
47
|
-
[concat(",", sort(required_branches)), ignore_branches_value, concat(",", sort(missing))],
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# ── helpers ──────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
target_steps contains step if {
|
|
54
|
-
some job in object.get(input, "jobs", {})
|
|
55
|
-
some step in object.get(job, "steps", [])
|
|
56
|
-
uses := object.get(step, "uses", "")
|
|
57
|
-
is_string(uses)
|
|
58
|
-
contains(uses, target_action_marker)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
has_ignore_branches_value if {
|
|
62
|
-
some step in target_steps
|
|
63
|
-
v := object.get(object.get(step, "with", {}), "ignore_branches", null)
|
|
64
|
-
is_string(v)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
default ignore_branches_value := ""
|
|
68
|
-
|
|
69
|
-
ignore_branches_value := values[0] if {
|
|
70
|
-
values := [v |
|
|
71
|
-
some step in target_steps
|
|
72
|
-
v := object.get(object.get(step, "with", {}), "ignore_branches", null)
|
|
73
|
-
is_string(v)
|
|
74
|
-
]
|
|
75
|
-
count(values) > 0
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
parsed_ignore_tokens(value) := {lower(trim_space(part)) |
|
|
79
|
-
some part in split(value, ",")
|
|
80
|
-
trim_space(part) != ""
|
|
81
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-clean_merged_ignore_branches.mjs
|
|
4
|
-
resource: npm/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 9fe2cc60
|
|
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` визначає набір шляхів репозиторію, які правило має враховувати під час перевірки. Воно свідомо не охоплює `.git` і `.github`, щоб не аналізувати службові дані Git та конфігурацію GitHub. Правило працює read-only: лише повідомляє про невідповідність очікуваній структурі, не змінюючи файли чи зовнішні сховища.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` визначає правило виправлення, яке забезпечує наявність workflow для очищення merged branches.
|
|
21
|
-
|
|
22
|
-
2. Правило орієнтується на шаблонний стан файлу `.github/workflows/clean-merged-branch.yml`, щоб уніфікувати поведінку репозиторіїв щодо прибирання вже злитих гілок.
|
|
23
|
-
|
|
24
|
-
3. Під час перевірки свідомо не обходить службові шляхи `.github` і `.git`, окрім цільового workflow-файлу, який є предметом виправлення.
|
|
25
|
-
|
|
26
|
-
4. `patterns` лише описує очікуване виправлення й не виконує самостійного запису у файлову систему чи інші сховища.
|
|
27
|
-
|
|
28
|
-
## Гарантії поведінки
|
|
29
|
-
|
|
30
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
31
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/abie/clean_merged_ignore_branches
|
|
4
|
-
resource: npm/rules/abie/clean_merged_ignore_branches/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| --------------------------------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-clean_merged_ignore_branches.mjs](fix-clean_merged_ignore_branches.md) | JS Module |
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createTemplateFixPattern } from '../../../scripts/lib/fix/template-deep-merge.mjs'
|
|
2
|
-
|
|
3
|
-
export const patterns = [
|
|
4
|
-
createTemplateFixPattern({
|
|
5
|
-
id: 'abie-clean_merged_ignore_branches-template',
|
|
6
|
-
targetPath: '.github/workflows/clean-merged-branch.yml'
|
|
7
|
-
})
|
|
8
|
-
]
|
package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# abie-specific шар поверх ga.clean_merged_branch:
|
|
2
|
-
# у `clean-merged-branch.yml` крок phpdocker-io/github-actions-delete-abandoned-branches
|
|
3
|
-
# має містити `ignore_branches: dev,ua` (обовʼязкові гілки для abie-проєктів).
|
|
4
|
-
jobs:
|
|
5
|
-
cleanup_old_branches:
|
|
6
|
-
steps:
|
|
7
|
-
- uses: phpdocker-io/github-actions-delete-abandoned-branches
|
|
8
|
-
with:
|
|
9
|
-
ignore_branches: dev,ua
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-lint_docker_yml.mjs
|
|
4
|
-
resource: npm/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 551913c9
|
|
7
|
-
model: openai-codex/gpt-5.5
|
|
8
|
-
tier: cloud-avg
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.98
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
`patterns` визначає поведінку правила для обходу проєкту без змін у файловій системі чи зовнішніх сховищах. Службові директорії `.github` і `.git` свідомо не входять до перевірюваних шляхів, щоб правило працювало лише з релевантним кодом проєкту.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` визначає правило автоматичного виправлення для workflow перевірки Docker.
|
|
21
|
-
|
|
22
|
-
2. `patterns` забезпечує наявність стандартного файлу `.github/workflows/lint-docker.yml`, щоб проєкт мав єдиний CI-контроль Docker-конфігурації.
|
|
23
|
-
|
|
24
|
-
3. `patterns` працює як read-only опис виправлення: сам модуль не змінює файлову систему чи зовнішні сховища.
|
|
25
|
-
|
|
26
|
-
4. Під час супровідного обходу шляхів свідомо не аналізуються `.github` і `.git`, щоб не змішувати службові директорії з основним кодом проєкту.
|
|
27
|
-
|
|
28
|
-
## Гарантії поведінки
|
|
29
|
-
|
|
30
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
31
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/docker/lint_docker_yml
|
|
4
|
-
resource: npm/rules/docker/lint_docker_yml/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| ------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-lint_docker_yml.mjs](fix-lint_docker_yml.md) | JS Module |
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
## Перевірка `.github/workflows/lint-docker.yml`
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `docker.lint_docker_yml`
|
|
4
|
-
|
|
5
|
-
Цільовий файл: `.github/workflows/lint-docker.yml`
|
|
6
|
-
|
|
7
|
-
Перевіряє, що CI-workflow lint-docker відповідає канонічному сніпету:
|
|
8
|
-
|
|
9
|
-
- `on.push.paths` містить усі три glob-маски: `**/Dockerfile`, `**/*.Dockerfile`, `**/*.dockerfile`
|
|
10
|
-
- присутні кроки `uses: actions/checkout@v6` та `uses: ./.github/actions/setup-bun-deps`
|
|
11
|
-
- крок install hadolint містить рядок із версією `v2.12.0` (substring-перевірка)
|
|
12
|
-
- крок lint містить `n-rules lint docker --no-fix`
|
|
13
|
-
|
|
14
|
-
Канонічний шаблон: [lint-docker.yml.snippet.yml](./template/lint-docker.yml.snippet.yml)
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# Перевірка `.github/workflows/lint-docker.yml` (docker.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
-
# Структура --data сформована з template/lint-docker.yml.snippet.yml.
|
|
5
|
-
# Path-маркери, hadolint версія (substring "v2.12.0" у run), setup-bun-deps
|
|
6
|
-
# composite, `n-rules lint docker --no-fix` substring — все читається з template's snippet.
|
|
7
|
-
# Універсальні workflow-перевірки — у `ga.workflow_common`.
|
|
8
|
-
package docker.lint_docker_yml
|
|
9
|
-
|
|
10
|
-
import rego.v1
|
|
11
|
-
|
|
12
|
-
# Очікувані літерали з template.
|
|
13
|
-
expected_paths := {p | some p in data.template.snippet.on.push.paths}
|
|
14
|
-
|
|
15
|
-
# Required uses set — з template's steps.
|
|
16
|
-
expected_uses_set contains u if {
|
|
17
|
-
some step in data.template.snippet.jobs["lint-docker"].steps
|
|
18
|
-
u := object.get(step, "uses", "")
|
|
19
|
-
u != ""
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
# Required run substrings — collected per-step з template.
|
|
23
|
-
expected_run_substrings contains r if {
|
|
24
|
-
some step in data.template.snippet.jobs["lint-docker"].steps
|
|
25
|
-
r := object.get(step, "run", "")
|
|
26
|
-
r != ""
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
# Аліаси на input.
|
|
30
|
-
all_step_uses contains u if {
|
|
31
|
-
some job in object.get(input, "jobs", {})
|
|
32
|
-
some step in object.get(job, "steps", [])
|
|
33
|
-
u := object.get(step, "uses", "")
|
|
34
|
-
u != ""
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
all_run_text := concat("\n", [run_text |
|
|
38
|
-
some job in object.get(input, "jobs", {})
|
|
39
|
-
some step in object.get(job, "steps", [])
|
|
40
|
-
run_text := step_run_to_text(step)
|
|
41
|
-
])
|
|
42
|
-
|
|
43
|
-
# conftest парсить YAML 1.1, тож канонічний `on:` без лапок стає булевим ключем
|
|
44
|
-
# `true` (як у `ga.lint_ga`). Тому читаємо через `input["true"]`.
|
|
45
|
-
push_paths_set := {p |
|
|
46
|
-
some p in object.get(object.get(object.get(input, "true", {}), "push", {}), "paths", [])
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# ── deny: on.push.paths subset-of ──────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
deny contains msg if {
|
|
52
|
-
some required in expected_paths
|
|
53
|
-
not required in push_paths_set
|
|
54
|
-
msg := sprintf("lint-docker.yml: on.push.paths має містити %q (docker.mdc)", [required])
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# ── deny: required uses present ────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
deny contains msg if {
|
|
60
|
-
some required_use in expected_uses_set
|
|
61
|
-
not required_use in all_step_uses
|
|
62
|
-
msg := sprintf("lint-docker.yml: відсутній крок `uses: %s` (docker.mdc)", [required_use])
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
# ── deny: required run substrings ──────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
deny contains msg if {
|
|
68
|
-
some required_run in expected_run_substrings
|
|
69
|
-
not contains(all_run_text, required_run)
|
|
70
|
-
msg := sprintf("lint-docker.yml: жоден крок run не містить %q (docker.mdc)", [required_run])
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
# ── helpers ────────────────────────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
step_run_to_text(step) := step.run if is_string(step.run)
|
|
76
|
-
|
|
77
|
-
else := concat("\n", [s | some s in step.run]) if is_array(step.run)
|
|
78
|
-
|
|
79
|
-
else := ""
|