@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.
Files changed (113) hide show
  1. package/.claude-template/hooks/.gitignore.snippet +9 -0
  2. package/CHANGELOG.md +12 -0
  3. package/bin/n-rules.js +83 -21
  4. package/package.json +1 -1
  5. package/rules/abie/main.mdc +0 -6
  6. package/rules/js/check/docs/main.md +1 -1
  7. package/rules/js/check/main.mjs +6 -10
  8. package/rules/js/main.mdc +0 -1
  9. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +4 -1
  10. package/rules/npm-module/main.mdc +0 -2
  11. package/rules/php/tooling/docs/main.md +1 -1
  12. package/rules/php/tooling/main.mjs +2 -7
  13. package/rules/python/tooling/docs/main.md +1 -1
  14. package/rules/python/tooling/main.mjs +2 -7
  15. package/rules/rego/lib/docs/run-external-tool.md +1 -2
  16. package/rules/rego/lib/run-external-tool.mjs +4 -1
  17. package/rules/security/main.mdc +0 -1
  18. package/rules/style/main.mdc +0 -1
  19. package/rules/style/tooling/docs/main.md +1 -1
  20. package/rules/style/tooling/main.mjs +2 -7
  21. package/rules/tauri/release/release.mdc +1 -1
  22. package/rules/text/formatting/docs/main.md +1 -1
  23. package/rules/text/formatting/main.mjs +11 -11
  24. package/rules/text/main.mdc +0 -1
  25. package/scripts/docs/skills-cli.md +3 -3
  26. package/scripts/docs/sync-claude-config.md +1 -1
  27. package/scripts/lib/acp-runner.mjs +8 -10
  28. package/scripts/lib/docs/acp-runner.md +5 -5
  29. package/scripts/lib/docs/mirror-parity.md +1 -1
  30. package/scripts/lib/mirror-parity.mjs +49 -16
  31. package/scripts/skills-cli.mjs +1 -1
  32. package/scripts/sync-claude-config.mjs +2 -2
  33. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +0 -19
  34. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +0 -81
  35. package/rules/abie/clean_merged_ignore_branches/concern.json +0 -11
  36. package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +0 -31
  37. package/rules/abie/clean_merged_ignore_branches/docs/index.md +0 -9
  38. package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +0 -8
  39. package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +0 -9
  40. package/rules/docker/lint_docker_yml/concern.json +0 -12
  41. package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +0 -31
  42. package/rules/docker/lint_docker_yml/docs/index.md +0 -9
  43. package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +0 -5
  44. package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +0 -14
  45. package/rules/docker/lint_docker_yml/lint_docker_yml.rego +0 -79
  46. package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +0 -41
  47. package/rules/js/lint_js_yml/concern.json +0 -12
  48. package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +0 -28
  49. package/rules/js/lint_js_yml/docs/index.md +0 -9
  50. package/rules/js/lint_js_yml/fix-lint_js_yml.mjs +0 -5
  51. package/rules/js/lint_js_yml/lint_js_yml.mdc +0 -14
  52. package/rules/js/lint_js_yml/lint_js_yml.rego +0 -117
  53. package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +0 -40
  54. package/rules/k8s/lint_k8s_yml/concern.json +0 -12
  55. package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +0 -30
  56. package/rules/k8s/lint_k8s_yml/docs/index.md +0 -9
  57. package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +0 -5
  58. package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +0 -57
  59. package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +0 -43
  60. package/rules/npm-module/npm_publish_yml/concern.json +0 -13
  61. package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +0 -28
  62. package/rules/npm-module/npm_publish_yml/docs/index.md +0 -9
  63. package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +0 -8
  64. package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +0 -49
  65. package/rules/php/lint_php_yml/concern.json +0 -12
  66. package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +0 -28
  67. package/rules/php/lint_php_yml/docs/index.md +0 -9
  68. package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +0 -5
  69. package/rules/php/lint_php_yml/lint_php_yml.mdc +0 -23
  70. package/rules/php/lint_php_yml/lint_php_yml.rego +0 -34
  71. package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +0 -47
  72. package/rules/python/lint_python_yml/concern.json +0 -12
  73. package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +0 -27
  74. package/rules/python/lint_python_yml/docs/index.md +0 -9
  75. package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +0 -5
  76. package/rules/python/lint_python_yml/lint_python_yml.mdc +0 -12
  77. package/rules/python/lint_python_yml/lint_python_yml.rego +0 -68
  78. package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +0 -41
  79. package/rules/rust/lint_rust_yml/concern.json +0 -14
  80. package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +0 -28
  81. package/rules/rust/lint_rust_yml/docs/index.md +0 -9
  82. package/rules/rust/lint_rust_yml/fix-lint_rust_yml.mjs +0 -5
  83. package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +0 -17
  84. package/rules/rust/lint_rust_yml/lint_rust_yml.rego +0 -79
  85. package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +0 -44
  86. package/rules/rust/toolchain_cache/concern.json +0 -14
  87. package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +0 -35
  88. package/rules/rust/toolchain_cache/docs/index.md +0 -12
  89. package/rules/rust/toolchain_cache/docs/main.md +0 -57
  90. package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +0 -117
  91. package/rules/rust/toolchain_cache/main.mjs +0 -177
  92. package/rules/rust/toolchain_cache/toolchain_cache.mdc +0 -23
  93. package/rules/security/lint_security_yml/concern.json +0 -14
  94. package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +0 -31
  95. package/rules/security/lint_security_yml/docs/index.md +0 -9
  96. package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +0 -8
  97. package/rules/security/lint_security_yml/lint_security_yml.mdc +0 -7
  98. package/rules/security/lint_security_yml/lint_security_yml.rego +0 -33
  99. package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +0 -31
  100. package/rules/style/lint_style_yml/concern.json +0 -12
  101. package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +0 -31
  102. package/rules/style/lint_style_yml/docs/index.md +0 -9
  103. package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +0 -5
  104. package/rules/style/lint_style_yml/lint_style_yml.mdc +0 -13
  105. package/rules/style/lint_style_yml/lint_style_yml.rego +0 -38
  106. package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +0 -39
  107. package/rules/text/lint_text/concern.json +0 -11
  108. package/rules/text/lint_text/docs/fix-lint_text.md +0 -28
  109. package/rules/text/lint_text/docs/index.md +0 -9
  110. package/rules/text/lint_text/fix-lint_text.mjs +0 -5
  111. package/rules/text/lint_text/lint_text.mdc +0 -19
  112. package/rules/text/lint_text/lint_text.rego +0 -104
  113. package/rules/text/lint_text/template/lint-text.yml.snippet.yml +0 -61
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ dist/
3
+ *.secret
4
+
5
+ # @7n/rules (adr) — локальні артефакти Stop-hook, не коміти
6
+ .claude/hooks/*.log
7
+ .claude/hooks/.normalize-state
8
+ .claude/hooks/.normalize.lock
9
+ .claude/scheduled_tasks.lock
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
- 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.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -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: 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`, бандловані адаптери `@agentclientprotocol/codex-acp` і `@agentclientprotocol/claude-agent-acp`), а не сирий `stdin`/`stdout`-піпінг.
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-раннер автоапрувляє `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
35
+ - ACP-раннер автоматично схвалює `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
36
36
  - Лише `claude` — deprecated: друкує попередження й лишається як fallback, доки не налаштовано pi-модель. `cursor`/`codex` — повноцінні раннери без попередження.
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: sync-claude-config.mjs
4
4
  resource: npm/scripts/sync-claude-config.mjs
5
5
  docgen:
6
- crc: e4513e7a
6
+ crc: 177ad3ea
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 85
9
9
  ---
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Виконання скіла через зовнішнього ACP-агента (Agent Client Protocol,
3
3
  * agentclientprotocol.com) — JSON-RPC поверх stdio замість сирого
4
- * `stdin`/`stdout`-піпінгу CLI.
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`-запису бандлованого npm-пакета-адаптера.
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-файлу бандлованого ACP-адаптера з його `package.json`.
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` для скіл-раннера: автоапрув дозволів, стрім тексту в stdout,
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
- автоапрувляє через `pickAutoPermissionOptionId`, `sessionUpdate` стрімить текстові
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 (перевіряється до спавну
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: mirror-parity.mjs
4
4
  resource: npm/scripts/lib/mirror-parity.mjs
5
5
  docgen:
6
- crc: 91a0c9f2
6
+ crc: 1e89b486
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
9
  ---