@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-rules.js +83 -21
  3. package/package.json +1 -2
  4. package/rules/abie/main.mdc +0 -6
  5. package/rules/js/check/docs/main.md +1 -1
  6. package/rules/js/check/main.mjs +6 -10
  7. package/rules/js/main.mdc +0 -1
  8. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +4 -1
  9. package/rules/npm-module/main.mdc +0 -2
  10. package/rules/php/tooling/docs/main.md +1 -1
  11. package/rules/php/tooling/main.mjs +2 -7
  12. package/rules/python/tooling/docs/main.md +1 -1
  13. package/rules/python/tooling/main.mjs +2 -7
  14. package/rules/rego/lib/docs/run-external-tool.md +1 -2
  15. package/rules/rego/lib/run-external-tool.mjs +4 -1
  16. package/rules/security/main.mdc +0 -1
  17. package/rules/style/main.mdc +0 -1
  18. package/rules/style/tooling/docs/main.md +1 -1
  19. package/rules/style/tooling/main.mjs +2 -7
  20. package/rules/tauri/release/release.mdc +1 -1
  21. package/rules/text/formatting/docs/main.md +1 -1
  22. package/rules/text/formatting/main.mjs +11 -11
  23. package/rules/text/main.mdc +0 -1
  24. package/scripts/docs/skills-cli.md +1 -1
  25. package/scripts/lib/acp-runner.mjs +26 -33
  26. package/scripts/lib/docs/acp-runner.md +5 -5
  27. package/scripts/lib/docs/mirror-parity.md +1 -1
  28. package/scripts/lib/mirror-parity.mjs +49 -16
  29. package/scripts/skills-cli.mjs +35 -26
  30. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +0 -19
  31. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +0 -81
  32. package/rules/abie/clean_merged_ignore_branches/concern.json +0 -11
  33. package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +0 -31
  34. package/rules/abie/clean_merged_ignore_branches/docs/index.md +0 -9
  35. package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +0 -8
  36. package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +0 -9
  37. package/rules/docker/lint_docker_yml/concern.json +0 -12
  38. package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +0 -31
  39. package/rules/docker/lint_docker_yml/docs/index.md +0 -9
  40. package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +0 -5
  41. package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +0 -14
  42. package/rules/docker/lint_docker_yml/lint_docker_yml.rego +0 -79
  43. package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +0 -41
  44. package/rules/js/lint_js_yml/concern.json +0 -12
  45. package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +0 -28
  46. package/rules/js/lint_js_yml/docs/index.md +0 -9
  47. package/rules/js/lint_js_yml/fix-lint_js_yml.mjs +0 -5
  48. package/rules/js/lint_js_yml/lint_js_yml.mdc +0 -14
  49. package/rules/js/lint_js_yml/lint_js_yml.rego +0 -117
  50. package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +0 -40
  51. package/rules/k8s/lint_k8s_yml/concern.json +0 -12
  52. package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +0 -30
  53. package/rules/k8s/lint_k8s_yml/docs/index.md +0 -9
  54. package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +0 -5
  55. package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +0 -57
  56. package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +0 -43
  57. package/rules/npm-module/npm_publish_yml/concern.json +0 -13
  58. package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +0 -28
  59. package/rules/npm-module/npm_publish_yml/docs/index.md +0 -9
  60. package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +0 -8
  61. package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +0 -49
  62. package/rules/php/lint_php_yml/concern.json +0 -12
  63. package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +0 -28
  64. package/rules/php/lint_php_yml/docs/index.md +0 -9
  65. package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +0 -5
  66. package/rules/php/lint_php_yml/lint_php_yml.mdc +0 -23
  67. package/rules/php/lint_php_yml/lint_php_yml.rego +0 -34
  68. package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +0 -47
  69. package/rules/python/lint_python_yml/concern.json +0 -12
  70. package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +0 -27
  71. package/rules/python/lint_python_yml/docs/index.md +0 -9
  72. package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +0 -5
  73. package/rules/python/lint_python_yml/lint_python_yml.mdc +0 -12
  74. package/rules/python/lint_python_yml/lint_python_yml.rego +0 -68
  75. package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +0 -41
  76. package/rules/rust/lint_rust_yml/concern.json +0 -14
  77. package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +0 -28
  78. package/rules/rust/lint_rust_yml/docs/index.md +0 -9
  79. package/rules/rust/lint_rust_yml/fix-lint_rust_yml.mjs +0 -5
  80. package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +0 -17
  81. package/rules/rust/lint_rust_yml/lint_rust_yml.rego +0 -79
  82. package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +0 -44
  83. package/rules/rust/toolchain_cache/concern.json +0 -14
  84. package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +0 -35
  85. package/rules/rust/toolchain_cache/docs/index.md +0 -12
  86. package/rules/rust/toolchain_cache/docs/main.md +0 -57
  87. package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +0 -117
  88. package/rules/rust/toolchain_cache/main.mjs +0 -177
  89. package/rules/rust/toolchain_cache/toolchain_cache.mdc +0 -23
  90. package/rules/security/lint_security_yml/concern.json +0 -14
  91. package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +0 -31
  92. package/rules/security/lint_security_yml/docs/index.md +0 -9
  93. package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +0 -8
  94. package/rules/security/lint_security_yml/lint_security_yml.mdc +0 -7
  95. package/rules/security/lint_security_yml/lint_security_yml.rego +0 -33
  96. package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +0 -31
  97. package/rules/style/lint_style_yml/concern.json +0 -12
  98. package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +0 -31
  99. package/rules/style/lint_style_yml/docs/index.md +0 -9
  100. package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +0 -5
  101. package/rules/style/lint_style_yml/lint_style_yml.mdc +0 -13
  102. package/rules/style/lint_style_yml/lint_style_yml.rego +0 -38
  103. package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +0 -39
  104. package/rules/text/lint_text/concern.json +0 -11
  105. package/rules/text/lint_text/docs/fix-lint_text.md +0 -28
  106. package/rules/text/lint_text/docs/index.md +0 -9
  107. package/rules/text/lint_text/fix-lint_text.mjs +0 -5
  108. package/rules/text/lint_text/lint_text.mdc +0 -19
  109. package/rules/text/lint_text/lint_text.rego +0 -104
  110. 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.7.0] - 2026-07-16
4
+
5
+ ### Changed
6
+
7
+ - `skill cursor/codex` тепер виконуються через `@7n/llm-lib/acp` (napi-міст до Rust `llm_cascade::acp`) замість власного JS ACP-клієнта; deprecated `skill claude` лишається окремим JS-шимом (`acp-runner.mjs`), бо Rust-крейт `claude` не моделює
8
+
9
+ ## [1.6.0] - 2026-07-16
10
+
11
+ ### Changed
12
+
13
+ - CI-концерни провайдерів: 12 workflow-концернів (lint_*_yml, npm_publish_yml, toolchain_cache, clean_merged_ignore_branches) перенесено у @7n/rules-ci-github; multi-dir mdc-інлайн mixin-концернів у дзеркала правил; рефакторинг aggregateRuleSources
14
+
3
15
  ## [1.5.1] - 2026-07-16
4
16
 
5
17
  ### Fixed
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
- names = await discoverBundledRuleNames(dir) // ядро: відсутність каталогу — фатальна помилка
270
- } else {
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
- for (const name of names) {
278
- if (!sources.has(name)) sources.set(name, dir)
303
+ try {
304
+ out.push(await discoverBundledRuleNames(dir))
305
+ } catch {
306
+ out.push([])
279
307
  }
280
308
  }
281
- return { names: sources.keys().toArray(), sources }
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
- const withTemplates = await inlineTemplateLinks(text, dirname(bundledPath))
478
- return appendDiscoveredMdcFiles(withTemplates, dirname(bundledPath))
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 sourceDir = ruleSources.get(normalizeRuleName(rule)) ?? bundledRulesDir
1257
- const content = await readBundledRuleContent(rule, sourceDir)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.5.1",
3
+ "version": "1.7.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -85,7 +85,6 @@
85
85
  },
86
86
  "optionalDependencies": {
87
87
  "@agentclientprotocol/claude-agent-acp": "^0.59.0",
88
- "@agentclientprotocol/codex-acp": "^1.1.2",
89
88
  "@earendil-works/pi-ai": "0.80.2",
90
89
  "@earendil-works/pi-coding-agent": "0.80.2"
91
90
  },
@@ -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`).
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/js/check/main.mjs
5
5
  docgen:
6
- crc: 8858d876
6
+ crc: 0b9c8a32
7
7
  model: manual
8
8
  ---
9
9
 
@@ -235,21 +235,17 @@ async function checkOxlintRc(passFn, failFn, cwd) {
235
235
  }
236
236
 
237
237
  /**
238
- * FS-existence для `lint-js.yml` + cross-file перевірка, що `lint.yml` (якщо існує)
239
- * не дублює лінт JS-кроки. Структуру `lint-js.yml` (`actions/checkout@v6`,
240
- * `persist-credentials: false`, `setup-bun-deps`, `bunx oxlint/eslint/jscpd .`,
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
- if (existsSync(join(cwd, '.github/workflows/lint-js.yml'))) {
248
- passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @7n/rules fix → js_lint.lint_js_yml)')
249
- } else {
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)
@@ -1,3 +1,6 @@
1
1
  {
2
- "recommendations": ["dbaeumer.vscode-eslint", "github.vscode-github-actions", "oxc.oxc-vscode"]
2
+ "recommendations": [
3
+ "dbaeumer.vscode-eslint",
4
+ "oxc.oxc-vscode"
5
+ ]
3
6
  }
@@ -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)
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/php/tooling/main.mjs
5
5
  docgen:
6
- crc: 91381cd7
6
+ crc: c49aaee6
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -24,12 +24,7 @@ export function lint(ctx) {
24
24
  fail('package.json не знайдено в корені — додай (php.mdc)')
25
25
  }
26
26
 
27
- const wfPath = '.github/workflows/lint-php.yml'
28
- if (existsSync(wfPath)) {
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
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/python/tooling/main.mjs
5
5
  docgen:
6
- crc: c46acc00
6
+ crc: 98ec5b8e
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
9
  issues: surzhik,judge:inaccurate:0.98
@@ -39,12 +39,7 @@ export function lint(ctx) {
39
39
  fail('package.json не знайдено в корені — додай (python.mdc)')
40
40
  }
41
41
 
42
- const wfPath = '.github/workflows/lint-python.yml'
43
- if (existsSync(join(cwd, wfPath))) {
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: 298f9908
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) return existsSync(resolve(root, FULL_TARGET)) ? [FULL_TARGET] : []
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-файл.
@@ -11,5 +11,4 @@ version: '2.1'
11
11
 
12
12
  `npx @7n/rules fix security`
13
13
 
14
- - [lint-security.yml.snippet.yml](./lint_security_yml/template/lint-security.yml.snippet.yml)
15
14
  - [package.json.deny.json](./package_json/template/package.json.deny.json)
@@ -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)
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/style/tooling/main.mjs
5
5
  docgen:
6
- crc: 4333b971
6
+ crc: 015d1c9a
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
9
  issues: anchor-miss:extensions.json,anchor-miss:settings.json,judge:inaccurate:0.99
@@ -67,12 +67,7 @@ export async function lint(ctx) {
67
67
  fail('.stylelintignore не існує — створи з вмістом: dist/')
68
68
  }
69
69
 
70
- const wfPath = '.github/workflows/lint-style.yml'
71
- if (existsSync(join(cwd, wfPath))) {
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
- осідає в репозиторії без релізу, доки хтось не помітить і не докомітить change-файл окремо.
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,
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/text/formatting/main.mjs
5
5
  docgen:
6
- crc: 2621711a
6
+ crc: ac0f783c
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: best-of-2:retry-won,judge:inaccurate:0.99
@@ -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
- const wf = await readFile(lintTextWf, 'utf8')
109
- const root = parseWorkflowYaml(wf)
110
- const canonRun = 'n-rules lint text --no-fix'
111
- const ok = root ? anyRunStepIncludes(root, canonRun) : wf.includes(canonRun)
112
- if (ok) {
113
- passFn(`lint-text.yml викликає ${canonRun}`)
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('.github/workflows/lint-text.yml не існуєствори згідно n-text.mdc')
118
+ failFn(`lint-text.yml має містити крок ${canonRun} (CI read-only, нуль мутацій)`)
119
119
  }
120
120
  }
121
121
 
@@ -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: 33d8696c
6
+ crc: 1a8f83e4
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Виконання скіла через зовнішнього ACP-агента (Agent Client Protocol,
3
- * agentclientprotocol.com) — JSON-RPC поверх stdio замість сирого
4
- * `stdin`/`stdout`-піпінгу CLI.
2
+ * Deprecated JS ACP-шим для `skill claude` (Agent Client Protocol,
3
+ * agentclientprotocol.com) — JSON-RPC поверх stdio.
5
4
  *
6
- * Заміняє колишній `runLlmCli` (spawnSync + `-p`/`exec -`) для `cursor`/`codex`/`claude`:
7
- * підʼєднання йде через офіційний TS SDK `@zed-industries/agent-client-protocol`,
8
- * а дозволи на tool-calls (`session/request_permission`) автоапрувляться без участі
9
- * людини паритет із сьогоднішнім non-interactive `-p`-режимом (full user-trust,
10
- * без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
5
+ * `cursor`/`codex` мігрували на `@7n/llm-lib/acp` (napi-міст до
6
+ * `llm_cascade::acp` та сама протокольна логіка, але в Rust, без
7
+ * дублювання в JS). `claude` лишається тут як окремий JS-шим, бо Rust-крейт
8
+ * `AcpAgentKind` його не моделює (лише `Cursor`/`Codex`); підʼєднання йде
9
+ * через офіційний TS SDK `@zed-industries/agent-client-protocol`, а дозволи
10
+ * на tool-calls (`session/request_permission`) автоматично схвалюються без
11
+ * участі людини — паритет із колишнім non-interactive `-p`-режимом
12
+ * (full user-trust, без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
11
13
  *
12
- * `cursor` — нативний ACP-режим самого `cursor-agent` (`cursor-agent acp`).
13
- * `codex`/`claude` офіційні адаптери (`@agentclientprotocol/codex-acp`,
14
- * `@agentclientprotocol/claude-agent-acp`), що бандлять свій рушій — зовнішній
15
- * бінарник `codex`/`claude` у PATH більше не потрібен.
14
+ * `claude` — офіційний адаптер (`@agentclientprotocol/claude-agent-acp`), що
15
+ * вбудовує свій рушій — зовнішній бінарник `claude` у PATH не потрібен.
16
16
  */
17
17
 
18
18
  import { spawn } from 'node:child_process'
@@ -25,13 +25,11 @@ import { Readable, Writable } from 'node:stream'
25
25
  const require = createRequire(import.meta.url)
26
26
 
27
27
  /**
28
- * Команда запуску ACP-агента на провайдер. `cursor` зовнішній бінарник у PATH;
29
- * `codex`/`claude` — резолвляться з `bin`-запису бандлованого npm-пакета-адаптера.
30
- * @type {Record<'cursor' | 'codex' | 'claude', { bin: string, args: string[] } | { adapterPackage: string }>}
28
+ * Команда запуску ACP-агента на провайдер. Резолвиться з `bin`-запису
29
+ * вбудованого npm-пакета-адаптера.
30
+ * @type {Record<'claude', { adapterPackage: string }>}
31
31
  */
32
32
  export const ACP_AGENT_COMMANDS = {
33
- cursor: { bin: 'cursor-agent', args: ['acp'] },
34
- codex: { adapterPackage: '@agentclientprotocol/codex-acp' },
35
33
  claude: { adapterPackage: '@agentclientprotocol/claude-agent-acp' }
36
34
  }
37
35
 
@@ -45,8 +43,8 @@ export function stopReasonToExitCode(stopReason) {
45
43
  }
46
44
 
47
45
  /**
48
- * Резолвить абсолютний шлях до bin-файлу бандлованого ACP-адаптера з його `package.json`.
49
- * @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/codex-acp`, …)
46
+ * Резолвить абсолютний шлях до bin-файлу вбудованого ACP-адаптера з його `package.json`.
47
+ * @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/claude-agent-acp`)
50
48
  * @returns {string} абсолютний шлях до виконуваного файлу адаптера
51
49
  */
52
50
  export function resolveAdapterBin(adapterPackage) {
@@ -71,7 +69,7 @@ export function pickAutoPermissionOptionId(options) {
71
69
  }
72
70
 
73
71
  /**
74
- * ACP `Client` для скіл-раннера: автоапрув дозволів, стрім тексту в stdout,
72
+ * ACP `Client` для скіл-раннера: автоматичне схвалення дозволів, стрім тексту в stdout,
75
73
  * пряма реалізація файлового вводу-виводу поверх `node:fs` (full-trust режим).
76
74
  */
77
75
  class AcpSkillClient {
@@ -84,7 +82,7 @@ class AcpSkillClient {
84
82
 
85
83
  /**
86
84
  * @param {{ options: { optionId: string, kind: string }[] }} params запит дозволу від агента
87
- * @returns {Promise<{ outcome: { outcome: 'selected', optionId: string } }>} автообраний варіант
85
+ * @returns {Promise<{ outcome: { outcome: 'selected', optionId: string } }>} автоматично обраний варіант
88
86
  */
89
87
  requestPermission(params) {
90
88
  return Promise.resolve({ outcome: { outcome: 'selected', optionId: pickAutoPermissionOptionId(params.options) } })
@@ -121,12 +119,13 @@ class AcpSkillClient {
121
119
  }
122
120
 
123
121
  /**
124
- * Виконує скіл через зовнішнього ACP-агента (`cursor`/`codex`/`claude`).
125
- * @param {'cursor' | 'codex' | 'claude'} kind провайдер
122
+ * Виконує скіл через зовнішнього ACP-агента. Єдиний лишений провайдер —
123
+ * deprecated `claude` (`cursor`/`codex` див. `@7n/llm-lib/acp`).
124
+ * @param {'claude'} kind провайдер
126
125
  * @param {string} prompt готовий промпт скіла
127
126
  * @param {string} projectDir робочий каталог сесії агента
128
127
  * @param {(line: string) => void} logError вивід помилок
129
- * @param {{ acp?: object, spawnFn?: typeof spawn, out?: (chunk: string) => void, resolveAdapterBin?: (pkg: string) => string, isBinaryInPath?: (bin: string) => boolean }} [deps] інжекти для тестів
128
+ * @param {{ acp?: object, spawnFn?: typeof spawn, out?: (chunk: string) => void, resolveAdapterBin?: (pkg: string) => string }} [deps] інжекти для тестів
130
129
  * @returns {Promise<number>} exit code (0 — `end_turn`, 1 — інакше)
131
130
  */
132
131
  export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}) {
@@ -134,15 +133,9 @@ export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}
134
133
  const out = deps.out ?? (chunk => stdout.write(chunk))
135
134
  const resolveBin = deps.resolveAdapterBin ?? resolveAdapterBin
136
135
 
137
- const command = ACP_AGENT_COMMANDS[kind]
138
- const { bin, args } =
139
- 'adapterPackage' in command
140
- ? { bin: process.execPath, args: [resolveBin(command.adapterPackage)] }
141
- : command
142
-
143
- if ('bin' in command && deps.isBinaryInPath && !deps.isBinaryInPath(command.bin)) {
144
- throw new Error(`\`${command.bin}\` not found in PATH. Install ${kind} CLI (ACP mode) or use \`skill pi\`.`)
145
- }
136
+ const { adapterPackage } = ACP_AGENT_COMMANDS[kind]
137
+ const bin = process.execPath
138
+ const args = [resolveBin(adapterPackage)]
146
139
 
147
140
  const acp = deps.acp ?? (await import('@zed-industries/agent-client-protocol'))
148
141
  const child = spawnFn(bin, args, { cwd: projectDir, stdio: ['pipe', 'pipe', 'inherit'], env: process.env })