@7n/rules 1.5.0 → 1.6.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/.claude-template/hooks/.gitignore.snippet +9 -0
- package/CHANGELOG.md +12 -0
- package/bin/n-rules.js +83 -21
- package/package.json +1 -1
- 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 +3 -3
- package/scripts/docs/sync-claude-config.md +1 -1
- package/scripts/lib/acp-runner.mjs +8 -10
- 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 +1 -1
- package/scripts/sync-claude-config.mjs +2 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0] - 2026-07-16
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- CI-концерни провайдерів: 12 workflow-концернів (lint_*_yml, npm_publish_yml, toolchain_cache, clean_merged_ignore_branches) перенесено у @7n/rules-ci-github; multi-dir mdc-інлайн mixin-концернів у дзеркала правил; рефакторинг aggregateRuleSources
|
|
8
|
+
|
|
9
|
+
## [1.5.1] - 2026-07-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- sync-claude-config: відновлено `.gitignore.snippet` для ADR Stop-hook (загублено при переструктуруванні rules/adr на per-concern layout)
|
|
14
|
+
|
|
3
15
|
## [1.5.0] - 2026-07-15
|
|
4
16
|
|
|
5
17
|
### Added
|
package/bin/n-rules.js
CHANGED
|
@@ -258,27 +258,78 @@ async function readRootPackageJsonSafe() {
|
|
|
258
258
|
* Агрегує імена правил по rules-каталогах (ядро перше, перший власник виграє)
|
|
259
259
|
* і будує мапу `ruleId → rulesDir` для копіювання mdc.
|
|
260
260
|
* @param {string[]} rulesDirs упорядковані rules-каталоги (ядро + плагіни)
|
|
261
|
-
* @returns {Promise<{ names: string[], sources: Map<string, string> }>} імена
|
|
261
|
+
* @returns {Promise<{ names: string[], sources: Map<string, string>, extras: Map<string, string[]> }>} імена правил, їх джерела і mixin-теки
|
|
262
262
|
*/
|
|
263
263
|
async function aggregateRuleSources(rulesDirs) {
|
|
264
264
|
/** @type {Map<string, string>} */
|
|
265
265
|
const sources = new Map()
|
|
266
|
+
/** @type {Map<string, string[]>} каталоги `rules/<id>/` mixin-джерел (не власник main.mdc) */
|
|
267
|
+
const extras = new Map()
|
|
268
|
+
const perDirNames = await listRuleNamesPerDir(rulesDirs)
|
|
269
|
+
for (const [i, dir] of rulesDirs.entries()) {
|
|
270
|
+
for (const name of perDirNames[i]) {
|
|
271
|
+
if (!sources.has(name)) sources.set(name, dir)
|
|
272
|
+
}
|
|
273
|
+
if (i > 0) await collectMixinDirs(dir, perDirNames[i], extras)
|
|
274
|
+
}
|
|
275
|
+
// Тека з main.mdc у плагіні для правила, яким володіє інше джерело, — теж mixin (дублікат id).
|
|
276
|
+
for (const [i, dir] of rulesDirs.entries()) {
|
|
277
|
+
if (i === 0) continue
|
|
278
|
+
for (const name of perDirNames[i]) {
|
|
279
|
+
if (sources.get(name) === dir) continue
|
|
280
|
+
const list = extras.get(name) ?? []
|
|
281
|
+
list.push(join(dir, name))
|
|
282
|
+
extras.set(name, list)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { names: sources.keys().toArray(), sources, extras }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Імена правил (теки з `main.mdc`) кожного rules-каталогу. Для ядра (перший елемент)
|
|
290
|
+
* відсутність каталогу — фатальна помилка; для плагінів — порожній список
|
|
291
|
+
* (уже повідомлено у resolve-plugins).
|
|
292
|
+
* @param {string[]} rulesDirs упорядковані rules-каталоги
|
|
293
|
+
* @returns {Promise<string[][]>} імена правил per-dir у тому ж порядку
|
|
294
|
+
*/
|
|
295
|
+
async function listRuleNamesPerDir(rulesDirs) {
|
|
296
|
+
/** @type {string[][]} */
|
|
297
|
+
const out = []
|
|
266
298
|
for (const [i, dir] of rulesDirs.entries()) {
|
|
267
|
-
let names
|
|
268
299
|
if (i === 0) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
names = await discoverBundledRuleNames(dir)
|
|
273
|
-
} catch {
|
|
274
|
-
continue // плагін без валідного rules/ — уже повідомлено у resolve-plugins
|
|
275
|
-
}
|
|
300
|
+
out.push(await discoverBundledRuleNames(dir))
|
|
301
|
+
continue
|
|
276
302
|
}
|
|
277
|
-
|
|
278
|
-
|
|
303
|
+
try {
|
|
304
|
+
out.push(await discoverBundledRuleNames(dir))
|
|
305
|
+
} catch {
|
|
306
|
+
out.push([])
|
|
279
307
|
}
|
|
280
308
|
}
|
|
281
|
-
return
|
|
309
|
+
return out
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Додає у `extras` mixin-теки плагіна: підкаталоги `rules/<id>/` БЕЗ `main.mdc`
|
|
314
|
+
* (плагін доповнює правило іншого джерела концернами).
|
|
315
|
+
* @param {string} dir rules-каталог плагіна
|
|
316
|
+
* @param {string[]} ownNames імена повних правил цього каталогу (з main.mdc)
|
|
317
|
+
* @param {Map<string, string[]>} extras акумулятор mixin-тек (мутується)
|
|
318
|
+
* @returns {Promise<void>}
|
|
319
|
+
*/
|
|
320
|
+
async function collectMixinDirs(dir, ownNames, extras) {
|
|
321
|
+
let entries
|
|
322
|
+
try {
|
|
323
|
+
entries = await readdir(dir, { withFileTypes: true })
|
|
324
|
+
} catch {
|
|
325
|
+
return // нечитабельний rules/ плагіна — вже пропущений вище
|
|
326
|
+
}
|
|
327
|
+
for (const e of entries) {
|
|
328
|
+
if (!e.isDirectory() || e.name.startsWith('.') || ownNames.includes(e.name)) continue
|
|
329
|
+
const list = extras.get(e.name) ?? []
|
|
330
|
+
list.push(join(dir, e.name))
|
|
331
|
+
extras.set(e.name, list)
|
|
332
|
+
}
|
|
282
333
|
}
|
|
283
334
|
|
|
284
335
|
/**
|
|
@@ -461,11 +512,16 @@ function normalizeRuleName(ruleName) {
|
|
|
461
512
|
/**
|
|
462
513
|
* Читає вміст правила з каталогу `rules/<id>/main.mdc` установленого пакету
|
|
463
514
|
* (наприклад `node_modules/@7n/rules/rules/<id>/main.mdc` або кеш npx).
|
|
515
|
+
* Mixin-концерни: `extraRuleDirs` — каталоги `rules/<id>/` того самого правила з ІНШИХ
|
|
516
|
+
* джерел (плагінів); їхні concern-mdc доінлайнюються після концернів власника, тож
|
|
517
|
+
* `.cursor/rules/n-<id>.mdc` містить і провайдер-специфічні розділи (напр. lint_js_yml
|
|
518
|
+
* з `@7n/rules-ci-github` чи lint_pipeline_js з `@7n/rules-ci-azure`).
|
|
464
519
|
* @param {string} rule елемент масиву rules з `.n-rules.json`
|
|
465
|
-
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
|
|
520
|
+
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела (власник main.mdc)
|
|
521
|
+
* @param {string[]} [extraRuleDirs] каталоги `rules/<id>/` mixin-джерел (без main.mdc)
|
|
466
522
|
* @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
|
|
467
523
|
*/
|
|
468
|
-
async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
524
|
+
async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR, extraRuleDirs = []) {
|
|
469
525
|
const id = normalizeRuleName(rule)
|
|
470
526
|
const bundledPath = join(bundledRulesDir, id, 'main.mdc')
|
|
471
527
|
if (!existsSync(bundledPath)) {
|
|
@@ -474,8 +530,12 @@ async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR)
|
|
|
474
530
|
)
|
|
475
531
|
}
|
|
476
532
|
const text = await readFile(bundledPath, 'utf8')
|
|
477
|
-
|
|
478
|
-
|
|
533
|
+
let out = await inlineTemplateLinks(text, dirname(bundledPath))
|
|
534
|
+
out = await appendDiscoveredMdcFiles(out, dirname(bundledPath))
|
|
535
|
+
for (const extraDir of extraRuleDirs) {
|
|
536
|
+
out = await appendDiscoveredMdcFiles(out, extraDir)
|
|
537
|
+
}
|
|
538
|
+
return out
|
|
479
539
|
}
|
|
480
540
|
|
|
481
541
|
/**
|
|
@@ -1243,9 +1303,10 @@ async function captureOutput(action) {
|
|
|
1243
1303
|
* @param {string} bundledRulesDir каталог `rules` пакету-джерела (fallback для правил без явного джерела)
|
|
1244
1304
|
* @param {string} rulesDir абсолютний шлях до `.cursor/rules`
|
|
1245
1305
|
* @param {Map<string, string>} [ruleSources] мапа `ruleId → rulesDir` власника правила (ядро/плагін)
|
|
1306
|
+
* @param {Map<string, string[]>} [ruleExtras] мапа `ruleId → rules/<id>-теки` mixin-джерел (concern-mdc доінлайнюються)
|
|
1246
1307
|
* @returns {Promise<{ successCount: number, failCount: number }>} статистика копіювання
|
|
1247
1308
|
*/
|
|
1248
|
-
async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources = new Map()) {
|
|
1309
|
+
async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources = new Map(), ruleExtras = new Map()) {
|
|
1249
1310
|
let successCount = 0
|
|
1250
1311
|
let failCount = 0
|
|
1251
1312
|
for (const rule of rules) {
|
|
@@ -1253,8 +1314,9 @@ async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSource
|
|
|
1253
1314
|
const destPath = join(rulesDir, fileName)
|
|
1254
1315
|
try {
|
|
1255
1316
|
process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1317
|
+
const id = normalizeRuleName(rule)
|
|
1318
|
+
const sourceDir = ruleSources.get(id) ?? bundledRulesDir
|
|
1319
|
+
const content = await readBundledRuleContent(rule, sourceDir, ruleExtras.get(id) ?? [])
|
|
1258
1320
|
await writeFile(destPath, content, 'utf8')
|
|
1259
1321
|
console.log(`✅`)
|
|
1260
1322
|
successCount++
|
|
@@ -1406,9 +1468,9 @@ async function runSync() {
|
|
|
1406
1468
|
await mkdir(rulesDir, { recursive: true })
|
|
1407
1469
|
// Джерела правил: ядро + rules/ плагінів (резолв кешований після readConfig).
|
|
1408
1470
|
const allRulesDirs = resolveRulesDirs(projectRoot, config, bundledRulesDir).map(d => d.rulesDir)
|
|
1409
|
-
const { sources: ruleSources } = await aggregateRuleSources(allRulesDirs)
|
|
1471
|
+
const { sources: ruleSources, extras: ruleExtras } = await aggregateRuleSources(allRulesDirs)
|
|
1410
1472
|
const { successCount, failCount } = await captureOutput(async () => {
|
|
1411
|
-
const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources)
|
|
1473
|
+
const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources, ruleExtras)
|
|
1412
1474
|
return { ...stats, fail: stats.failCount }
|
|
1413
1475
|
})
|
|
1414
1476
|
|
package/package.json
CHANGED
package/rules/abie/main.mdc
CHANGED
|
@@ -24,12 +24,6 @@ version: '1.22'
|
|
|
24
24
|
bun add -d @nitra/abie-shared
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
## Git branches
|
|
28
|
-
|
|
29
|
-
У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev** та **ua** (разом з іншими гілками, якщо потрібно):
|
|
30
|
-
|
|
31
|
-
- abie-specific канон (action marker + required ignore_branches tokens): [clean-merged-branch.yml.snippet.yml](./clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml)
|
|
32
|
-
|
|
33
27
|
## Швидкий gate через conftest (Rego)
|
|
34
28
|
|
|
35
29
|
Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/rules/abie/policy/`** (запускається через **`bun run lint-rego`** для `*_test.rego` юніт-тестів і через **`npx @7n/rules fix abie`** для прогону по реальних YAML — деталі в **conftest.mdc** / **n-rego.mdc**). JS у **`js/<concern>.mjs`** authoritative — rego тільки швидкий gate для одиничного маніфеста (зокрема через IDE-розширення `tsandall.opa`).
|
package/rules/js/check/main.mjs
CHANGED
|
@@ -235,21 +235,17 @@ async function checkOxlintRc(passFn, failFn, cwd) {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* заборона `--fix` у CI) валідує `npm/policy/js_lint/lint_js_yml/`.
|
|
238
|
+
* Cross-file перевірка: `lint.yml` (якщо існує) не дублює лінт JS-кроки з `lint-js.yml`.
|
|
239
|
+
* Existence і структуру `lint-js.yml` валідує mixin-концерн `js/lint_js_yml`
|
|
240
|
+
* плагіна `@7n/rules-ci-github` (провайдер-специфіка — поза ядром).
|
|
242
241
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
243
242
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
244
243
|
* @param {string} cwd корінь репозиторію
|
|
245
244
|
*/
|
|
246
245
|
async function checkLintJsWorkflows(passFn, failFn, cwd) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
failFn('.github/workflows/lint-js.yml не існує — створи його (js.mdc)')
|
|
251
|
-
}
|
|
252
|
-
|
|
246
|
+
// Existence/структуру lint-js.yml вимагає провайдер-плагін @7n/rules-ci-github
|
|
247
|
+
// (mixin js/lint_js_yml, policy required) — ядро провайдер-агностичне і тут
|
|
248
|
+
// перевіряє лише крос-файловий дубляж, коли GitHub-workflow уже існують.
|
|
253
249
|
const lintYmlPath = join(cwd, '.github/workflows/lint.yml')
|
|
254
250
|
if (existsSync(lintYmlPath)) {
|
|
255
251
|
const lintYml = await readFile(lintYmlPath, 'utf8')
|
package/rules/js/main.mdc
CHANGED
|
@@ -13,7 +13,6 @@ version: '1.31'
|
|
|
13
13
|
|
|
14
14
|
Rego-пакети — запускаються `npx @7n/rules lint js` або `conftest`:
|
|
15
15
|
|
|
16
|
-
- [lint-js.yml.snippet.yml](./lint_js_yml/template/lint-js.yml.snippet.yml)
|
|
17
16
|
- [.jscpd.json.snippet.json](./jscpd_config/template/.jscpd.json.snippet.json)
|
|
18
17
|
- [extensions.json.snippet.json](./vscode_extensions/template/extensions.json.snippet.json)
|
|
19
18
|
- [package.json.snippet.json](./package_json/template/package.json.snippet.json)
|
|
@@ -31,8 +31,6 @@ Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.g
|
|
|
31
31
|
|
|
32
32
|
Rego-пакети (запускаються через `npx @7n/rules fix`):
|
|
33
33
|
|
|
34
|
-
- `npm_module.npm_publish_yml` — template-driven перевірка `.github/workflows/npm-publish.yml` (deep-subset: усі обовʼязкові поля й кроки з канонічного сніпету).
|
|
35
34
|
- [package.json.snippet.json](./npm_package_json/template/package.json.snippet.json)
|
|
36
|
-
- [npm-publish.yml.snippet.yml](./npm_publish_yml/template/npm-publish.yml.snippet.yml)
|
|
37
35
|
- [package.json.snippet.json](./root_package_json/template/package.json.snippet.json)
|
|
38
36
|
- [tsconfig.emit-types.json.snippet.json](./emit_types_config/template/tsconfig.emit-types.json.snippet.json)
|
|
@@ -24,12 +24,7 @@ export function lint(ctx) {
|
|
|
24
24
|
fail('package.json не знайдено в корені — додай (php.mdc)')
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
pass(`${wfPath} є (структуру перевіряє npx @7n/rules fix → php.lint_php_yml)`)
|
|
30
|
-
} else {
|
|
31
|
-
fail(`${wfPath} не існує — створи згідно php.mdc`)
|
|
32
|
-
}
|
|
33
|
-
|
|
27
|
+
// Existence/структуру lint-php.yml вимагає провайдер-плагін @7n/rules-ci-github
|
|
28
|
+
// (mixin php/lint_php_yml) — ядро провайдер-агностичне.
|
|
34
29
|
return Promise.resolve(reporter.result())
|
|
35
30
|
}
|
|
@@ -39,12 +39,7 @@ export function lint(ctx) {
|
|
|
39
39
|
fail('package.json не знайдено в корені — додай (python.mdc)')
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
pass(`${wfPath} є (структуру перевіряє fix → python.lint_python_yml)`)
|
|
45
|
-
} else {
|
|
46
|
-
fail(`${wfPath} не існує — створи згідно python.mdc`)
|
|
47
|
-
}
|
|
48
|
-
|
|
42
|
+
// Existence/структуру lint-python.yml вимагає провайдер-плагін @7n/rules-ci-github
|
|
43
|
+
// (mixin python/lint_python_yml) — ядро провайдер-агностичне.
|
|
49
44
|
return Promise.resolve(reporter.result())
|
|
50
45
|
}
|
|
@@ -3,9 +3,8 @@ type: JS Module
|
|
|
3
3
|
title: run-external-tool.mjs
|
|
4
4
|
resource: npm/rules/rego/lib/run-external-tool.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 0f1da640
|
|
7
7
|
model: openai-codex/gpt-5.4-mini
|
|
8
|
-
tier: cloud-min
|
|
9
8
|
score: 100
|
|
10
9
|
issues: judge:inaccurate:0.99
|
|
11
10
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
@@ -24,7 +24,10 @@ const TEST_SUFFIX_RE = /_test\.rego$/u
|
|
|
24
24
|
* @returns {string[]} цілі для CLI-аргументів інструменту (можливо порожній список).
|
|
25
25
|
*/
|
|
26
26
|
export function resolveTargets(files, root) {
|
|
27
|
-
if (files === undefined)
|
|
27
|
+
if (files === undefined) {
|
|
28
|
+
// Full-режим: policy-корінь ядра + rego плагінів (монорепо @7n/rules)
|
|
29
|
+
return [FULL_TARGET, 'plugins'].filter(t => existsSync(resolve(root, t)))
|
|
30
|
+
}
|
|
28
31
|
const rego = files.filter(f => REGO_EXT_RE.test(f))
|
|
29
32
|
// `X_test.rego` імпортує сусідній `X.rego`: без нього regal флагує unresolved-import,
|
|
30
33
|
// тож до delta-цілей додаємо наявний сусідній policy-файл.
|
package/rules/security/main.mdc
CHANGED
package/rules/style/main.mdc
CHANGED
|
@@ -8,6 +8,5 @@ alwaysApply: false
|
|
|
8
8
|
Правило **style** для Vue-проєктів: Quasar як стильова система, SCSS-конвенції кольорів і відступів, фікси компонентів, stylelint через `@nitra/stylelint-config`.
|
|
9
9
|
|
|
10
10
|
- [settings.json.snippet.json](./vscode_settings/template/settings.json.snippet.json)
|
|
11
|
-
- [lint-style.yml.snippet.yml](./lint_style_yml/template/lint-style.yml.snippet.yml)
|
|
12
11
|
- [extensions.json.snippet.json](./vscode_extensions/template/extensions.json.snippet.json)
|
|
13
12
|
- [package.json.snippet.json](./package_json/template/package.json.snippet.json)
|
|
@@ -67,12 +67,7 @@ export async function lint(ctx) {
|
|
|
67
67
|
fail('.stylelintignore не існує — створи з вмістом: dist/')
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
pass(`${wfPath} є (структуру перевіряє npx @7n/rules fix → style_lint.lint_style_yml)`)
|
|
73
|
-
} else {
|
|
74
|
-
fail(`${wfPath} не існує — створи його`)
|
|
75
|
-
}
|
|
76
|
-
|
|
70
|
+
// Existence/структуру lint-style.yml вимагає провайдер-плагін @7n/rules-ci-github
|
|
71
|
+
// (mixin style/lint_style_yml) — ядро провайдер-агностичне.
|
|
77
72
|
return reporter.result()
|
|
78
73
|
}
|
|
@@ -10,7 +10,7 @@ Change-файл (`<app>/.changes/<ts>.md`) створюється **одразу
|
|
|
10
10
|
чіпає код Tauri-workspace — той самий момент, що lint і doc-files, а не окремий крок після
|
|
11
11
|
сигналу `changeset-missing` від delta-lint concern `changelog/presence`. Без нього мердж у
|
|
12
12
|
`main` не тригерить `changelog-release.yml` (тригер — `paths: [app/.changes/**]`), і зміна
|
|
13
|
-
осідає в репозиторії без релізу, доки хтось не помітить і не
|
|
13
|
+
осідає в репозиторії без релізу, доки хтось не помітить і не додасть change-файл у коміт окремо.
|
|
14
14
|
|
|
15
15
|
Команда: `npx @7n/n ch --bump <major|minor|patch> --section <Added|Changed|Fixed|Removed> --message "<опис>"`
|
|
16
16
|
(інтерактивно — `npx @7n/n ch`). Повна модель (база порівняння, формат CHANGELOG,
|
|
@@ -103,19 +103,19 @@ function checkTextConfigsExistence(passFn, failFn, cwd) {
|
|
|
103
103
|
* @param {string} cwd корінь репозиторію
|
|
104
104
|
*/
|
|
105
105
|
async function checkLintTextWorkflow(passFn, failFn, cwd) {
|
|
106
|
+
// Existence lint-text.yml вимагає провайдер-плагін @7n/rules-ci-github (mixin
|
|
107
|
+
// text/lint_text) — ядро провайдер-агностичне; тут лише перевірка вмісту,
|
|
108
|
+
// коли GitHub-workflow уже існує.
|
|
106
109
|
const lintTextWf = join(cwd, '.github/workflows/lint-text.yml')
|
|
107
|
-
if (existsSync(lintTextWf))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} else {
|
|
115
|
-
failFn(`lint-text.yml має містити крок ${canonRun} (CI — read-only, нуль мутацій)`)
|
|
116
|
-
}
|
|
110
|
+
if (!existsSync(lintTextWf)) return
|
|
111
|
+
const wf = await readFile(lintTextWf, 'utf8')
|
|
112
|
+
const root = parseWorkflowYaml(wf)
|
|
113
|
+
const canonRun = 'n-rules lint text --no-fix'
|
|
114
|
+
const ok = root ? anyRunStepIncludes(root, canonRun) : wf.includes(canonRun)
|
|
115
|
+
if (ok) {
|
|
116
|
+
passFn(`lint-text.yml викликає ${canonRun}`)
|
|
117
117
|
} else {
|
|
118
|
-
failFn(
|
|
118
|
+
failFn(`lint-text.yml має містити крок ${canonRun} (CI — read-only, нуль мутацій)`)
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
package/rules/text/main.mdc
CHANGED
|
@@ -17,7 +17,6 @@ Rego-пакети, що перевіряються через conftest (auto-dis
|
|
|
17
17
|
- [.cspell.json.deny.json](./cspell/template/.cspell.json.deny.json)
|
|
18
18
|
- [.cspell.json.contains.json](./cspell/template/.cspell.json.contains.json)
|
|
19
19
|
- [extensions.json.snippet.json](./vscode_extensions/template/extensions.json.snippet.json)
|
|
20
|
-
- [lint-text.yml.snippet.yml](./lint_text/template/lint-text.yml.snippet.yml)
|
|
21
20
|
- [package.json.deny.json](./package_json/template/package.json.deny.json)
|
|
22
21
|
|
|
23
22
|
**Український апостроф:** у словах вживай прямий апостроф U+0027 (`'`, як в `об'єкт`), а не типографський U+2019 (`’`); слова з апострофом додавай у масив `words` cspell-конфігу.
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: skills-cli.mjs
|
|
4
4
|
resource: npm/scripts/skills-cli.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: ce3d7d60
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -30,7 +30,7 @@ runSkillsCli виконує логіку командного інтерфейс
|
|
|
30
30
|
|
|
31
31
|
## Гарантії поведінки
|
|
32
32
|
|
|
33
|
-
- Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішньому ACP-агенту `cursor`/`codex`/`claude` через `runAcpRunner` (`./lib/acp-runner.mjs`) — JSON-RPC поверх stdio (`cursor-agent acp`,
|
|
33
|
+
- Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішньому ACP-агенту `cursor`/`codex`/`claude` через `runAcpRunner` (`./lib/acp-runner.mjs`) — JSON-RPC поверх stdio (`cursor-agent acp`, вбудовані адаптери `@agentclientprotocol/codex-acp` і `@agentclientprotocol/claude-agent-acp`), а не сирий `stdin`/`stdout`-pipe-передавання.
|
|
34
34
|
- Тира моделі для `pi`-runner береться з `main.json.tier` скіла (дефолт `max`).
|
|
35
|
-
- ACP-раннер
|
|
35
|
+
- ACP-раннер автоматично схвалює `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
|
|
36
36
|
- Лише `claude` — deprecated: друкує попередження й лишається як fallback, доки не налаштовано pi-модель. `cursor`/`codex` — повноцінні раннери без попередження.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Виконання скіла через зовнішнього ACP-агента (Agent Client Protocol,
|
|
3
3
|
* agentclientprotocol.com) — JSON-RPC поверх stdio замість сирого
|
|
4
|
-
* `stdin`/`stdout
|
|
4
|
+
* `stdin`/`stdout`-передавання через pipe CLI.
|
|
5
5
|
*
|
|
6
6
|
* Заміняє колишній `runLlmCli` (spawnSync + `-p`/`exec -`) для `cursor`/`codex`/`claude`:
|
|
7
7
|
* підʼєднання йде через офіційний TS SDK `@zed-industries/agent-client-protocol`,
|
|
8
|
-
* а дозволи на tool-calls (`session/request_permission`)
|
|
8
|
+
* а дозволи на tool-calls (`session/request_permission`) автоматично схвалюються без участі
|
|
9
9
|
* людини — паритет із сьогоднішнім non-interactive `-p`-режимом (full user-trust,
|
|
10
10
|
* без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
|
|
11
11
|
*
|
|
12
12
|
* `cursor` — нативний ACP-режим самого `cursor-agent` (`cursor-agent acp`).
|
|
13
13
|
* `codex`/`claude` — офіційні адаптери (`@agentclientprotocol/codex-acp`,
|
|
14
|
-
* `@agentclientprotocol/claude-agent-acp`), що
|
|
14
|
+
* `@agentclientprotocol/claude-agent-acp`), що вбудовують свій рушій — зовнішній
|
|
15
15
|
* бінарник `codex`/`claude` у PATH більше не потрібен.
|
|
16
16
|
*/
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ const require = createRequire(import.meta.url)
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Команда запуску ACP-агента на провайдер. `cursor` — зовнішній бінарник у PATH;
|
|
29
|
-
* `codex`/`claude` — резолвляться з `bin`-запису
|
|
29
|
+
* `codex`/`claude` — резолвляться з `bin`-запису вбудованого npm-пакета-адаптера.
|
|
30
30
|
* @type {Record<'cursor' | 'codex' | 'claude', { bin: string, args: string[] } | { adapterPackage: string }>}
|
|
31
31
|
*/
|
|
32
32
|
export const ACP_AGENT_COMMANDS = {
|
|
@@ -45,7 +45,7 @@ export function stopReasonToExitCode(stopReason) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Резолвить абсолютний шлях до bin-файлу
|
|
48
|
+
* Резолвить абсолютний шлях до bin-файлу вбудованого ACP-адаптера з його `package.json`.
|
|
49
49
|
* @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/codex-acp`, …)
|
|
50
50
|
* @returns {string} абсолютний шлях до виконуваного файлу адаптера
|
|
51
51
|
*/
|
|
@@ -71,7 +71,7 @@ export function pickAutoPermissionOptionId(options) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* ACP `Client` для скіл-раннера:
|
|
74
|
+
* ACP `Client` для скіл-раннера: автоматичне схвалення дозволів, стрім тексту в stdout,
|
|
75
75
|
* пряма реалізація файлового вводу-виводу поверх `node:fs` (full-trust режим).
|
|
76
76
|
*/
|
|
77
77
|
class AcpSkillClient {
|
|
@@ -84,7 +84,7 @@ class AcpSkillClient {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* @param {{ options: { optionId: string, kind: string }[] }} params запит дозволу від агента
|
|
87
|
-
* @returns {Promise<{ outcome: { outcome: 'selected', optionId: string } }>}
|
|
87
|
+
* @returns {Promise<{ outcome: { outcome: 'selected', optionId: string } }>} автоматично обраний варіант
|
|
88
88
|
*/
|
|
89
89
|
requestPermission(params) {
|
|
90
90
|
return Promise.resolve({ outcome: { outcome: 'selected', optionId: pickAutoPermissionOptionId(params.options) } })
|
|
@@ -136,9 +136,7 @@ export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}
|
|
|
136
136
|
|
|
137
137
|
const command = ACP_AGENT_COMMANDS[kind]
|
|
138
138
|
const { bin, args } =
|
|
139
|
-
'adapterPackage' in command
|
|
140
|
-
? { bin: process.execPath, args: [resolveBin(command.adapterPackage)] }
|
|
141
|
-
: command
|
|
139
|
+
'adapterPackage' in command ? { bin: process.execPath, args: [resolveBin(command.adapterPackage)] } : command
|
|
142
140
|
|
|
143
141
|
if ('bin' in command && deps.isBinaryInPath && !deps.isBinaryInPath(command.bin)) {
|
|
144
142
|
throw new Error(`\`${command.bin}\` not found in PATH. Install ${kind} CLI (ACP mode) or use \`skill pi\`.`)
|
|
@@ -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 (перевіряється до спавну
|