@7n/rules 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-rules.js +65 -17
  3. package/package.json +11 -1
  4. package/rules/abie/clean_merged_ignore_branches/concern.json +3 -0
  5. package/rules/docker/lint_docker_yml/concern.json +3 -0
  6. package/rules/js/lint_js_yml/concern.json +3 -0
  7. package/rules/k8s/lint_k8s_yml/concern.json +3 -0
  8. package/rules/npm-module/npm_publish_yml/concern.json +3 -0
  9. package/rules/php/lint_php_yml/concern.json +3 -0
  10. package/rules/python/lint_python_yml/concern.json +3 -0
  11. package/rules/rust/lint_rust_yml/concern.json +3 -0
  12. package/rules/rust/toolchain_cache/concern.json +10 -1
  13. package/rules/security/lint_security_yml/concern.json +3 -0
  14. package/rules/style/lint_style_yml/concern.json +3 -0
  15. package/rules/tauri/main.mdc +1 -1
  16. package/rules/tauri/release/release.mdc +15 -0
  17. package/rules/text/lint_text/concern.json +3 -0
  18. package/schemas/concern.json +86 -16
  19. package/schemas/n-rules.json +11 -1
  20. package/schemas/v8r-catalog.json +123 -27
  21. package/scripts/auto-rules.mjs +47 -6
  22. package/scripts/docs/auto-rules.md +3 -1
  23. package/scripts/docs/skills-cli.md +3 -2
  24. package/scripts/docs/upgrade-n-rules-and-install.md +3 -1
  25. package/scripts/lib/acp-runner.mjs +174 -0
  26. package/scripts/lib/concern-meta.mjs +8 -1
  27. package/scripts/lib/docs/acp-runner.md +58 -0
  28. package/scripts/lib/docs/concern-meta.md +1 -1
  29. package/scripts/lib/docs/read-n-rules-config-lite.md +3 -1
  30. package/scripts/lib/docs/resolve-plugins.md +17 -0
  31. package/scripts/lib/docs/run-conftest-batch.md +3 -1
  32. package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +3 -1
  33. package/scripts/lib/lint-surface/docs/run-detectors.md +5 -1
  34. package/scripts/lib/lint-surface/policy-lint-adapter.mjs +2 -0
  35. package/scripts/lib/lint-surface/run-detectors.mjs +72 -5
  36. package/scripts/lib/read-n-rules-config-lite.mjs +5 -3
  37. package/scripts/lib/resolve-plugins.mjs +248 -0
  38. package/scripts/lib/run-conftest-batch.mjs +3 -1
  39. package/scripts/skills-cli.mjs +30 -52
  40. package/scripts/upgrade-n-rules-and-install.mjs +5 -4
  41. package/rules/ga/clean_ga_workflows/clean_ga_workflows.mdc +0 -20
  42. package/rules/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -112
  43. package/rules/ga/clean_ga_workflows/concern.json +0 -10
  44. package/rules/ga/clean_ga_workflows/docs/fix-clean_ga_workflows.md +0 -33
  45. package/rules/ga/clean_ga_workflows/docs/index.md +0 -9
  46. package/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs +0 -8
  47. package/rules/ga/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +0 -26
  48. package/rules/ga/clean_merged_branch/clean_merged_branch.mdc +0 -24
  49. package/rules/ga/clean_merged_branch/clean_merged_branch.rego +0 -163
  50. package/rules/ga/clean_merged_branch/concern.json +0 -10
  51. package/rules/ga/clean_merged_branch/docs/fix-clean_merged_branch.md +0 -27
  52. package/rules/ga/clean_merged_branch/docs/index.md +0 -9
  53. package/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs +0 -8
  54. package/rules/ga/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +0 -37
  55. package/rules/ga/docs/index.md +0 -11
  56. package/rules/ga/git_ai/concern.json +0 -11
  57. package/rules/ga/git_ai/docs/fix-git_ai.md +0 -30
  58. package/rules/ga/git_ai/docs/index.md +0 -9
  59. package/rules/ga/git_ai/fix-git_ai.mjs +0 -5
  60. package/rules/ga/git_ai/git_ai.mdc +0 -19
  61. package/rules/ga/git_ai/git_ai.rego +0 -76
  62. package/rules/ga/git_ai/template/git-ai.yml.snippet.yml +0 -30
  63. package/rules/ga/lint_ga/concern.json +0 -10
  64. package/rules/ga/lint_ga/docs/fix-lint_ga.md +0 -32
  65. package/rules/ga/lint_ga/docs/index.md +0 -9
  66. package/rules/ga/lint_ga/fix-lint_ga.mjs +0 -5
  67. package/rules/ga/lint_ga/lint_ga.mdc +0 -21
  68. package/rules/ga/lint_ga/lint_ga.rego +0 -132
  69. package/rules/ga/lint_ga/template/lint-ga.yml.snippet.yml +0 -41
  70. package/rules/ga/main.json +0 -1
  71. package/rules/ga/main.mdc +0 -17
  72. package/rules/ga/vscode_extensions/concern.json +0 -11
  73. package/rules/ga/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
  74. package/rules/ga/vscode_extensions/docs/index.md +0 -11
  75. package/rules/ga/vscode_extensions/fix-vscode_extensions.mjs +0 -1
  76. package/rules/ga/vscode_extensions/template/extensions.json.snippet.json +0 -1
  77. package/rules/ga/vscode_extensions/vscode_extensions.mdc +0 -9
  78. package/rules/ga/vscode_extensions/vscode_extensions.rego +0 -18
  79. package/rules/ga/vscode_settings/concern.json +0 -11
  80. package/rules/ga/vscode_settings/docs/fix-vscode_settings.md +0 -26
  81. package/rules/ga/vscode_settings/docs/index.md +0 -9
  82. package/rules/ga/vscode_settings/fix-vscode_settings.mjs +0 -5
  83. package/rules/ga/vscode_settings/template/settings.json.snippet.json +0 -1
  84. package/rules/ga/vscode_settings/vscode_settings.mdc +0 -9
  85. package/rules/ga/vscode_settings/vscode_settings.rego +0 -22
  86. package/rules/ga/workflow_common/concern.json +0 -9
  87. package/rules/ga/workflow_common/template/uses-min-versions.snippet.json +0 -4
  88. package/rules/ga/workflow_common/workflow_common.mdc +0 -18
  89. package/rules/ga/workflow_common/workflow_common.rego +0 -288
  90. package/rules/ga/workflows/concern.json +0 -8
  91. package/rules/ga/workflows/docs/fix-workflows.md +0 -37
  92. package/rules/ga/workflows/docs/main.md +0 -38
  93. package/rules/ga/workflows/fix-workflows.mjs +0 -212
  94. package/rules/ga/workflows/main.mjs +0 -433
  95. package/rules/ga/workflows/workflows.mdc +0 -33
  96. package/rules/ga/zizmor_yml/concern.json +0 -11
  97. package/rules/ga/zizmor_yml/docs/fix-zizmor_yml.md +0 -28
  98. package/rules/ga/zizmor_yml/docs/index.md +0 -9
  99. package/rules/ga/zizmor_yml/fix-zizmor_yml.mjs +0 -3
  100. package/rules/ga/zizmor_yml/template/zizmor.yml.snippet.yml +0 -5
  101. package/rules/ga/zizmor_yml/zizmor_yml.mdc +0 -9
  102. package/rules/ga/zizmor_yml/zizmor_yml.rego +0 -27
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.0] - 2026-07-15
4
+
5
+ ### Added
6
+
7
+ - Нове правило `local-ai` (auto: завжди): інтеграція rtk (Rust Token Killer) для стискання виводу CLI-команд агентів. Sync вставляє fail-open PreToolUse hook у `.claude/settings.json` (`rtk hook claude`), preToolUse у `.cursor/hooks.json` (`rtk hook cursor`) та vendored pi-extension `.pi/extensions/rtk.ts`; від користувача потрібен лише `brew install rtk-ai/tap/rtk`. Вимкнення — `disable-rules: ["local-ai"]`.
8
+ - Механізм плагінів: поле `plugins` у .n-rules.json + автодетект CI-провайдера (файли CI → repository.url) з авто-встановленням devDependency; multi-dir завантаження правил (ядро + плагіни, merge концернів), маніфест плагіна `n-rules` (capabilities/handlers); правило `ga` винесено у @7n/rules-ci-github
9
+ - tauri/release: change-файл — обовʼязковий крок вирішення задачі (як lint/doc-files), не post-hoc фікс на сигнал `changelog/presence`.
10
+
11
+ ### Changed
12
+
13
+ - docs(adr): merge pending ADR normalization batch
14
+
3
15
  ## [1.4.1] - 2026-07-15
4
16
 
5
17
  ### Fixed
package/bin/n-rules.js CHANGED
@@ -83,6 +83,7 @@ import { injectWorktreeNotice } from '../scripts/lib/worktree-notice.mjs'
83
83
  import { injectRootNotice } from '../scripts/lib/root-notice.mjs'
84
84
  import { listProjectRulesMdcFiles } from '../scripts/lib/list-project-rules-mdc.mjs'
85
85
  import { ensureNRulesInRootDevDependencies } from '../scripts/ensure-n-rules-dev-dependencies.mjs'
86
+ import { resolvePluginList, resolveRulesDirs } from '../scripts/lib/resolve-plugins.mjs'
86
87
  import { assertCwdIsProjectRoot } from '../scripts/lib/assert-project-root.mjs'
87
88
  import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
88
89
  import { syncGitignoreWorktree } from '../scripts/lib/sync-gitignore-worktree.mjs'
@@ -253,6 +254,33 @@ async function readRootPackageJsonSafe() {
253
254
  }
254
255
  }
255
256
 
257
+ /**
258
+ * Агрегує імена правил по rules-каталогах (ядро перше, перший власник виграє)
259
+ * і будує мапу `ruleId → rulesDir` для копіювання mdc.
260
+ * @param {string[]} rulesDirs упорядковані rules-каталоги (ядро + плагіни)
261
+ * @returns {Promise<{ names: string[], sources: Map<string, string> }>} імена правил і їх джерела
262
+ */
263
+ async function aggregateRuleSources(rulesDirs) {
264
+ /** @type {Map<string, string>} */
265
+ const sources = new Map()
266
+ for (const [i, dir] of rulesDirs.entries()) {
267
+ let names
268
+ 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
+ }
276
+ }
277
+ for (const name of names) {
278
+ if (!sources.has(name)) sources.set(name, dir)
279
+ }
280
+ }
281
+ return { names: sources.keys().toArray(), sources }
282
+ }
283
+
256
284
  /**
257
285
  * Зчитує конфіг .n-rules.json з поточної директорії
258
286
  * @param {{ bundledRulesDir?: string, bundledSkillsDir?: string }} [paths] каталоги з пакету-джерела (після `bun i` — зазвичай `node_modules/@7n/rules`)
@@ -263,7 +291,22 @@ async function readConfig(paths = {}) {
263
291
  const bundledSkillsDir = paths.bundledSkillsDir ?? BUNDLED_SKILLS_DIR
264
292
  await migrateLegacyConfigIfNeeded()
265
293
  const configPath = join(cwd(), CONFIG_FILE)
266
- const availableRules = await discoverBundledRuleNames(bundledRulesDir)
294
+
295
+ /** @type {Record<string, unknown> | null} */
296
+ let rawConfig = null
297
+ if (existsSync(configPath)) {
298
+ try {
299
+ rawConfig = JSON.parse(await readFile(configPath, 'utf8'))
300
+ } catch {
301
+ throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
302
+ }
303
+ }
304
+
305
+ // Плагіни: явне поле plugins конфігу або автодетект; sync-контекст — з установкою devDep.
306
+ // Результат детекту записуємо у конфіг (нижче), щоб hook/lint не залежали від детекту й мережі.
307
+ const detectedPlugins = Array.isArray(rawConfig?.plugins) ? null : resolvePluginList(cwd(), rawConfig)
308
+ const rulesDirs = resolveRulesDirs(cwd(), rawConfig, bundledRulesDir).map(d => d.rulesDir)
309
+ const { names: availableRules } = await aggregateRuleSources(rulesDirs)
267
310
  const availableSkills = await discoverBundledSkillNames(bundledSkillsDir)
268
311
 
269
312
  /**
@@ -291,7 +334,8 @@ async function readConfig(paths = {}) {
291
334
  root: cwd(),
292
335
  availableRules,
293
336
  packageJsonParsed: rootPkg,
294
- disableRules
337
+ disableRules,
338
+ rulesDirs
295
339
  })
296
340
  // Skills залежать від ефективного списку правил, який буде у конфізі після merge:
297
341
  // вже існуючі (опт-ін вручну) + auto-detected, мінус `disable-rules`. Без цього
@@ -335,15 +379,20 @@ async function readConfig(paths = {}) {
335
379
  if (merged['disable-skills']?.length) {
336
380
  normalized['disable-skills'] = merged['disable-skills']
337
381
  }
382
+ if (!('plugins' in parsedConfig) && detectedPlugins && detectedPlugins.length > 0) {
383
+ normalized.plugins = detectedPlugins
384
+ console.log(`🔌 Автодетект плагінів: ${detectedPlugins.join(', ')} — записано у ${CONFIG_FILE}\n`)
385
+ }
338
386
  return sortConfigIdArrays(normalized)
339
387
  }
340
388
 
341
- if (!existsSync(configPath)) {
389
+ if (rawConfig === null) {
342
390
  const rootPkg = await readRootPackageJsonSafe()
343
391
  const autoDetectedRules = await detectAutoRules({
344
392
  root: cwd(),
345
393
  availableRules,
346
- packageJsonParsed: rootPkg
394
+ packageJsonParsed: rootPkg,
395
+ rulesDirs
347
396
  })
348
397
  const autoDetectedSkills = detectAutoSkills({
349
398
  availableSkills,
@@ -352,7 +401,8 @@ async function readConfig(paths = {}) {
352
401
  const defaultConfig = sortConfigIdArrays({
353
402
  $schema: CONFIG_SCHEMA_URL,
354
403
  rules: autoDetectedRules.rules,
355
- skills: autoDetectedSkills.skills
404
+ skills: autoDetectedSkills.skills,
405
+ ...(detectedPlugins && detectedPlugins.length > 0 && { plugins: detectedPlugins })
356
406
  })
357
407
  await writeFile(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8')
358
408
  console.log(
@@ -360,14 +410,7 @@ async function readConfig(paths = {}) {
360
410
  )
361
411
  return defaultConfig
362
412
  }
363
- const raw = await readFile(configPath, 'utf8')
364
- /** @type {Record<string, unknown>} */
365
- let config
366
- try {
367
- config = JSON.parse(raw)
368
- } catch {
369
- throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
370
- }
413
+ const config = rawConfig
371
414
  logRuleMigrationsIfAny(config)
372
415
  const normalized = await normalizeConfigWithAutoRules(config)
373
416
  if (JSON.stringify(normalized) !== JSON.stringify(config)) {
@@ -1197,11 +1240,12 @@ async function captureOutput(action) {
1197
1240
  /**
1198
1241
  * Копіює керовані `.mdc` файли з пакету до `.cursor/rules`.
1199
1242
  * @param {string[]} rules список rules з конфігу
1200
- * @param {string} bundledRulesDir каталог `rules` пакету-джерела
1243
+ * @param {string} bundledRulesDir каталог `rules` пакету-джерела (fallback для правил без явного джерела)
1201
1244
  * @param {string} rulesDir абсолютний шлях до `.cursor/rules`
1245
+ * @param {Map<string, string>} [ruleSources] мапа `ruleId → rulesDir` власника правила (ядро/плагін)
1202
1246
  * @returns {Promise<{ successCount: number, failCount: number }>} статистика копіювання
1203
1247
  */
1204
- async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir) {
1248
+ async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources = new Map()) {
1205
1249
  let successCount = 0
1206
1250
  let failCount = 0
1207
1251
  for (const rule of rules) {
@@ -1209,7 +1253,8 @@ async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir) {
1209
1253
  const destPath = join(rulesDir, fileName)
1210
1254
  try {
1211
1255
  process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
1212
- const content = await readBundledRuleContent(rule, bundledRulesDir)
1256
+ const sourceDir = ruleSources.get(normalizeRuleName(rule)) ?? bundledRulesDir
1257
+ const content = await readBundledRuleContent(rule, sourceDir)
1213
1258
  await writeFile(destPath, content, 'utf8')
1214
1259
  console.log(`✅`)
1215
1260
  successCount++
@@ -1359,8 +1404,11 @@ async function runSync() {
1359
1404
 
1360
1405
  const rulesDir = join(cwd(), RULES_DIR)
1361
1406
  await mkdir(rulesDir, { recursive: true })
1407
+ // Джерела правил: ядро + rules/ плагінів (резолв кешований після readConfig).
1408
+ const allRulesDirs = resolveRulesDirs(projectRoot, config, bundledRulesDir).map(d => d.rulesDir)
1409
+ const { sources: ruleSources } = await aggregateRuleSources(allRulesDirs)
1362
1410
  const { successCount, failCount } = await captureOutput(async () => {
1363
- const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir)
1411
+ const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir, ruleSources)
1364
1412
  return { ...stats, fail: stats.failCount }
1365
1413
  })
1366
1414
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -48,6 +48,13 @@
48
48
  ],
49
49
  "type": "module",
50
50
  "types": "./types/bin/n-rules.d.ts",
51
+ "exports": {
52
+ ".": "./bin/n-rules.js",
53
+ "./package.json": "./package.json",
54
+ "./scripts/*": "./scripts/*",
55
+ "./schemas/*": "./schemas/*",
56
+ "./rules/*": "./rules/*"
57
+ },
51
58
  "scripts": {
52
59
  "test": "vitest run",
53
60
  "test:watch": "vitest",
@@ -58,6 +65,7 @@
58
65
  "dependencies": {
59
66
  "@7n/mt": "^0.5.1",
60
67
  "@7n/llm-lib": "^2.0.0",
68
+ "@zed-industries/agent-client-protocol": "^0.4.5",
61
69
  "ajv": "^8.20.0",
62
70
  "cli-progress": "^3.12.0",
63
71
  "eslint": "^10.4.1",
@@ -76,6 +84,8 @@
76
84
  "zod": "^4.4.3"
77
85
  },
78
86
  "optionalDependencies": {
87
+ "@agentclientprotocol/claude-agent-acp": "^0.59.0",
88
+ "@agentclientprotocol/codex-acp": "^1.1.2",
79
89
  "@earendil-works/pi-ai": "0.80.2",
80
90
  "@earendil-works/pi-coding-agent": "0.80.2"
81
91
  },
@@ -4,5 +4,8 @@
4
4
  "files": {
5
5
  "single": ".github/workflows/clean-merged-branch.yml"
6
6
  }
7
+ },
8
+ "requires": {
9
+ "capability": "ci:github"
7
10
  }
8
11
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-docker.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-js.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-k8s.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -6,5 +6,8 @@
6
6
  "single": ".github/workflows/npm-publish.yml"
7
7
  },
8
8
  "check": "template"
9
+ },
10
+ "requires": {
11
+ "capability": "ci:github"
9
12
  }
10
13
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-php.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-python.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -7,5 +7,8 @@
7
7
  "required": true
8
8
  },
9
9
  "missingMessage": ".github/workflows/lint-rust.yml не існує — створи з канонічним вмістом (rust.mdc)"
10
+ },
11
+ "requires": {
12
+ "capability": "ci:github"
10
13
  }
11
14
  }
@@ -1,5 +1,14 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
3
  "fixability": "config",
4
- "lint": { "scope": "full", "glob": [".github/workflows/*.yml", ".github/workflows/*.yaml"] }
4
+ "lint": {
5
+ "scope": "full",
6
+ "glob": [
7
+ ".github/workflows/*.yml",
8
+ ".github/workflows/*.yaml"
9
+ ]
10
+ },
11
+ "requires": {
12
+ "capability": "ci:github"
13
+ }
5
14
  }
@@ -7,5 +7,8 @@
7
7
  "required": true
8
8
  },
9
9
  "missingMessage": ".github/workflows/lint-security.yml не знайдено — створи за каноном security.mdc"
10
+ },
11
+ "requires": {
12
+ "capability": "ci:github"
10
13
  }
11
14
  }
@@ -5,5 +5,8 @@
5
5
  "files": {
6
6
  "single": ".github/workflows/lint-style.yml"
7
7
  }
8
+ },
9
+ "requires": {
10
+ "capability": "ci:github"
8
11
  }
9
12
  }
@@ -2,7 +2,7 @@
2
2
  description: Tauri
3
3
  globs: "**/src-tauri/**,**/tauri.conf.json"
4
4
  alwaysApply: false
5
- version: '1.8'
5
+ version: '1.9'
6
6
  ---
7
7
 
8
8
  Правило встановлює канонічні вимоги для Tauri-проєктів: виявлення маркерів у монорепо, VS Code-розширення, налаштування mutation-testing для platform bridge, компіляційну ізоляцію agent/LLM-логіки від app-shell, реалізацію Tool Surface поверх Tauri+Rust, реліз-flow через change-файли (тег → DMG/updater-артефакти), автооновлення через спільний `useUpdater()` з `@7n/tauri-components` та верифікацію застосунку LLM-агентом через `tauri-plugin-mcp-bridge`.
@@ -4,6 +4,21 @@
4
4
 
5
5
  Канон складається з двох workflow-файлів і відповідного `tauri.conf.json`-конфігу; референс — `.github/workflows/{changelog-release,release}.yml` та `src-tauri/tauri.conf.json` чотирьох production Tauri-репо, де цей flow вже впроваджено.
6
6
 
7
+ ### Change-файл — крок вирішення задачі, а не post-hoc lint-фікс
8
+
9
+ Change-файл (`<app>/.changes/<ts>.md`) створюється **одразу під час вирішення задачі**, що
10
+ чіпає код Tauri-workspace — той самий момент, що lint і doc-files, а не окремий крок після
11
+ сигналу `changeset-missing` від delta-lint concern `changelog/presence`. Без нього мердж у
12
+ `main` не тригерить `changelog-release.yml` (тригер — `paths: [app/.changes/**]`), і зміна
13
+ осідає в репозиторії без релізу, доки хтось не помітить і не докомітить change-файл окремо.
14
+
15
+ Команда: `npx @7n/n ch --bump <major|minor|patch> --section <Added|Changed|Fixed|Removed> --message "<опис>"`
16
+ (інтерактивно — `npx @7n/n ch`). Повна модель (база порівняння, формат CHANGELOG,
17
+ post-release-інваріант) — у `n-changelog.mdc` і `release/main.mdc`.
18
+
19
+ `changelog/presence` лишається gate-запобіжником на випадок, якщо крок пропущено (delta-lint
20
+ у CI/hook), а не первинним механізмом створення change-файлу.
21
+
7
22
  ### `changelog-release.yml` — бамп версії з change-файлів
8
23
 
9
24
  Тригериться на push у `main` за наявності change-файлів і на ручний dispatch:
@@ -4,5 +4,8 @@
4
4
  "files": {
5
5
  "single": ".github/workflows/lint-text.yml"
6
6
  }
7
+ },
8
+ "requires": {
9
+ "capability": "ci:github"
7
10
  }
8
11
  }
@@ -12,7 +12,11 @@
12
12
  },
13
13
  "fixability": {
14
14
  "type": "string",
15
- "enum": ["code", "config", "structural"],
15
+ "enum": [
16
+ "code",
17
+ "config",
18
+ "structural"
19
+ ],
16
20
  "description": "Маршрутизація fix-движка. Усі мітки виконують детерміновану фазу T0; різниця — що після неї. `code` (дефолт, поле можна опускати) — джерельні порушення, які LLM може осмислено переписати → повна pi-agent-ladder (local→cloud) до чистого re-detect. `config` — canon/settings/tooling конфіг з єдиною правильною формою (jscpd/oxfmtrc/tooling/package_json/*_yml): фікс детермінований (T0/regen з канону), LLM лише вгадав би невалідне → після T0 fail-fast без ladder. `structural` — дублікати/розташування/структура (jscpd_duplicates/test-location/package-structure): авто-правка ризикована й потребує людського розсуду → після T0 fail-fast без ladder. config vs structural різняться мотивом пропуску (детермінований канон vs надто ризиковано) — для телеметрії й майбутнього routing."
17
21
  },
18
22
  "check": {
@@ -27,7 +31,10 @@
27
31
  "properties": {
28
32
  "engine": {
29
33
  "type": "string",
30
- "enum": ["rego", "template"],
34
+ "enum": [
35
+ "rego",
36
+ "template"
37
+ ],
31
38
  "description": "rego → .rego через conftest; template → generic deep-subset. Канонічна заміна поля `check`."
32
39
  },
33
40
  "namespace": {
@@ -39,21 +46,40 @@
39
46
  {
40
47
  "type": "object",
41
48
  "additionalProperties": false,
42
- "required": ["single"],
49
+ "required": [
50
+ "single"
51
+ ],
43
52
  "properties": {
44
- "single": { "type": "string", "minLength": 1 },
45
- "required": { "type": "boolean" }
53
+ "single": {
54
+ "type": "string",
55
+ "minLength": 1
56
+ },
57
+ "required": {
58
+ "type": "boolean"
59
+ }
46
60
  }
47
61
  },
48
62
  {
49
63
  "type": "object",
50
64
  "additionalProperties": false,
51
- "required": ["walkGlob"],
65
+ "required": [
66
+ "walkGlob"
67
+ ],
52
68
  "properties": {
53
69
  "walkGlob": {
54
70
  "oneOf": [
55
- { "type": "string", "minLength": 1 },
56
- { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
71
+ {
72
+ "type": "string",
73
+ "minLength": 1
74
+ },
75
+ {
76
+ "type": "array",
77
+ "minItems": 1,
78
+ "items": {
79
+ "type": "string",
80
+ "minLength": 1
81
+ }
82
+ }
57
83
  ]
58
84
  }
59
85
  }
@@ -61,12 +87,24 @@
61
87
  {
62
88
  "type": "object",
63
89
  "additionalProperties": false,
64
- "required": ["glob"],
90
+ "required": [
91
+ "glob"
92
+ ],
65
93
  "properties": {
66
94
  "glob": {
67
95
  "oneOf": [
68
- { "type": "string", "minLength": 1 },
69
- { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
96
+ {
97
+ "type": "string",
98
+ "minLength": 1
99
+ },
100
+ {
101
+ "type": "array",
102
+ "minItems": 1,
103
+ "items": {
104
+ "type": "string",
105
+ "minLength": 1
106
+ }
107
+ }
70
108
  ]
71
109
  }
72
110
  }
@@ -75,7 +113,9 @@
75
113
  },
76
114
  "check": {
77
115
  "type": "string",
78
- "enum": ["template"],
116
+ "enum": [
117
+ "template"
118
+ ],
79
119
  "description": "template → generic deep-subset без .rego; без поля → Rego mode."
80
120
  },
81
121
  "missingMessage": {
@@ -87,22 +127,52 @@
87
127
  "lint": {
88
128
  "type": "object",
89
129
  "additionalProperties": false,
90
- "required": ["scope"],
130
+ "required": [
131
+ "scope"
132
+ ],
91
133
  "description": "Lint поверхня. main.mjs має експортувати lint(changed, cwd, opts).",
92
134
  "properties": {
93
135
  "scope": {
94
136
  "type": "string",
95
- "enum": ["per-file", "full"],
137
+ "enum": [
138
+ "per-file",
139
+ "full"
140
+ ],
96
141
  "description": "per-file: delta-режим; full: крос-файловий."
97
142
  },
98
143
  "glob": {
99
144
  "oneOf": [
100
- { "type": "string", "minLength": 1 },
101
- { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
145
+ {
146
+ "type": "string",
147
+ "minLength": 1
148
+ },
149
+ {
150
+ "type": "array",
151
+ "minItems": 1,
152
+ "items": {
153
+ "type": "string",
154
+ "minLength": 1
155
+ }
156
+ }
102
157
  ],
103
158
  "description": "per-file: фільтр delta-файлів; full: delta-тригер."
104
159
  }
105
160
  }
161
+ },
162
+ "requires": {
163
+ "type": "object",
164
+ "description": "Гейт активації: концерн виконується лише коли задекларована capability доступна (її надає встановлений плагін через блок n-rules.capabilities у своєму package.json). Приклад: {\"capability\": \"ci:github\"} — концерн вимагає GitHub Actions і спить у репозиторіях без плагіна @7n/rules-ci-github.",
165
+ "additionalProperties": false,
166
+ "required": [
167
+ "capability"
168
+ ],
169
+ "properties": {
170
+ "capability": {
171
+ "type": "string",
172
+ "pattern": "^[a-z][a-z0-9:-]*$",
173
+ "description": "Ім'я capability (конвенція <тип>:<назва>, напр. ci:github, ci:azure, lang:rust)"
174
+ }
175
+ }
106
176
  }
107
177
  }
108
178
  }
@@ -27,6 +27,14 @@
27
27
  "minLength": 1
28
28
  }
29
29
  },
30
+ "plugins": {
31
+ "type": "array",
32
+ "description": "npm-пакети-плагіни з додатковими правилами (rules/<id>/ за контрактом ядра). Якщо поле відсутнє — CLI автоматично визначає CI-провайдера (файли CI, потім repository.url) і дописує сюди результат. Порожній масив — плагіни вимкнено.",
33
+ "items": {
34
+ "type": "string",
35
+ "pattern": "^(@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"
36
+ }
37
+ },
30
38
  "disable-rules": {
31
39
  "type": "array",
32
40
  "description": "Список rule id (без префікса n-), які не потрібно автододавати в `.n-rules.json` під час запуску CLI.",
@@ -61,5 +69,7 @@
61
69
  "default": true
62
70
  }
63
71
  },
64
- "required": ["rules"]
72
+ "required": [
73
+ "rules"
74
+ ]
65
75
  }