@7n/rules 1.3.1 → 1.4.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 (95) hide show
  1. package/.pi-template/extensions/docs/index.md +9 -0
  2. package/.pi-template/extensions/docs/rtk.md +33 -0
  3. package/.pi-template/extensions/rtk.ts +102 -0
  4. package/CHANGELOG.md +12 -0
  5. package/bin/n-rules.js +1 -0
  6. package/docs/index.md +0 -2
  7. package/docs/vitest.config.md +1 -1
  8. package/package.json +1 -1
  9. package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +31 -0
  10. package/rules/abie/clean_merged_ignore_branches/docs/index.md +9 -0
  11. package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +6 -1
  12. package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +31 -0
  13. package/rules/docker/lint_docker_yml/docs/index.md +9 -0
  14. package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +3 -1
  15. package/rules/ga/clean_ga_workflows/docs/fix-clean_ga_workflows.md +33 -0
  16. package/rules/ga/clean_ga_workflows/docs/index.md +9 -0
  17. package/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs +6 -1
  18. package/rules/ga/clean_merged_branch/docs/fix-clean_merged_branch.md +27 -0
  19. package/rules/ga/clean_merged_branch/docs/index.md +9 -0
  20. package/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs +6 -1
  21. package/rules/ga/git_ai/docs/fix-git_ai.md +30 -0
  22. package/rules/ga/git_ai/docs/index.md +9 -0
  23. package/rules/ga/git_ai/fix-git_ai.mjs +3 -1
  24. package/rules/ga/lint_ga/docs/fix-lint_ga.md +32 -0
  25. package/rules/ga/lint_ga/docs/index.md +9 -0
  26. package/rules/ga/lint_ga/fix-lint_ga.mjs +3 -1
  27. package/rules/ga/vscode_settings/docs/fix-vscode_settings.md +26 -0
  28. package/rules/ga/vscode_settings/docs/index.md +9 -0
  29. package/rules/ga/vscode_settings/fix-vscode_settings.mjs +3 -1
  30. package/rules/ga/zizmor_yml/docs/fix-zizmor_yml.md +28 -0
  31. package/rules/ga/zizmor_yml/docs/index.md +9 -0
  32. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +26 -0
  33. package/rules/js/jscpd_config/docs/index.md +9 -0
  34. package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +28 -0
  35. package/rules/js/lint_js_yml/docs/index.md +9 -0
  36. package/rules/js/package_json/docs/fix-package_json.md +26 -0
  37. package/rules/js/package_json/docs/index.md +9 -0
  38. package/rules/js/tooling/data/tooling/knip-canonical.json +1 -0
  39. package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +30 -0
  40. package/rules/k8s/lint_k8s_yml/docs/index.md +9 -0
  41. package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +3 -1
  42. package/rules/local-ai/main.json +1 -0
  43. package/rules/local-ai/main.mdc +85 -0
  44. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
  45. package/rules/npm-module/emit_types_config/docs/index.md +9 -0
  46. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +3 -1
  47. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +27 -0
  48. package/rules/npm-module/npm_package_json/docs/index.md +9 -0
  49. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +3 -1
  50. package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +28 -0
  51. package/rules/npm-module/npm_publish_yml/docs/index.md +9 -0
  52. package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +4 -1
  53. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +26 -0
  54. package/rules/npm-module/root_package_json/docs/index.md +9 -0
  55. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +3 -1
  56. package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +28 -0
  57. package/rules/php/lint_php_yml/docs/index.md +9 -0
  58. package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +3 -1
  59. package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +27 -0
  60. package/rules/python/lint_python_yml/docs/index.md +9 -0
  61. package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +3 -1
  62. package/rules/rego/vscode_settings/docs/fix-vscode_settings.md +27 -0
  63. package/rules/rego/vscode_settings/docs/index.md +9 -0
  64. package/rules/rego/vscode_settings/fix-vscode_settings.mjs +3 -1
  65. package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +28 -0
  66. package/rules/rust/lint_rust_yml/docs/index.md +9 -0
  67. package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +31 -0
  68. package/rules/security/lint_security_yml/docs/index.md +9 -0
  69. package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +6 -1
  70. package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +31 -0
  71. package/rules/style/lint_style_yml/docs/index.md +9 -0
  72. package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +3 -1
  73. package/rules/style/package_json/docs/fix-package_json.md +27 -0
  74. package/rules/style/package_json/docs/index.md +9 -0
  75. package/rules/style/vscode_settings/docs/fix-vscode_settings.md +27 -0
  76. package/rules/style/vscode_settings/docs/index.md +9 -0
  77. package/rules/style/vscode_settings/fix-vscode_settings.mjs +3 -1
  78. package/rules/text/lint_text/docs/fix-lint_text.md +28 -0
  79. package/rules/text/lint_text/docs/index.md +9 -0
  80. package/rules/text/oxfmtrc/docs/fix-oxfmtrc.md +26 -0
  81. package/rules/text/oxfmtrc/docs/index.md +9 -0
  82. package/rules/text/vscode_settings/docs/fix-vscode_settings.md +27 -0
  83. package/rules/text/vscode_settings/docs/index.md +9 -0
  84. package/rules/text/vscode_settings/fix-vscode_settings.mjs +3 -1
  85. package/rules/worktree/vscode_settings/docs/fix-vscode_settings.md +28 -0
  86. package/rules/worktree/vscode_settings/docs/index.md +9 -0
  87. package/rules/worktree/zed_settings/docs/fix-zed_settings.md +28 -0
  88. package/rules/worktree/zed_settings/docs/index.md +9 -0
  89. package/rules/worktree/zed_settings/fix-zed_settings.mjs +3 -1
  90. package/scripts/docs/skills-cli.md +1 -1
  91. package/scripts/docs/sync-claude-config.md +9 -4
  92. package/scripts/lib/fix/docs/index.md +4 -5
  93. package/scripts/lib/fix/docs/template-deep-merge.md +35 -0
  94. package/scripts/skills-cli.mjs +15 -4
  95. package/scripts/sync-claude-config.mjs +145 -36
@@ -18,7 +18,13 @@
18
18
  * - `.claude/hooks/normalize-decisions.sh` — fully owned bash-скрипт ADR normalize
19
19
  * Stop-hook (батч-нормалізація чернеток); умови — ті самі, що для `capture`.
20
20
  * - `.cursor/hooks.json` — **merge**: користувацькі hooks зберігаються; ADR stop
21
- * entries додаються, коли правило `adr` увімкнене, і видаляються, коли вимкнене.
21
+ * entries додаються, коли правило `adr` увімкнене, і видаляються, коли вимкнене;
22
+ * rtk preToolUse entry — аналогічно за правилом `local-ai`.
23
+ * - rtk (правило `local-ai`) — fail-open інтеграція компресора виводу CLI-команд:
24
+ * PreToolUse hook у `.claude/settings.json` (`rtk hook claude`), preToolUse у
25
+ * `.cursor/hooks.json` (`rtk hook cursor`) і vendored pi-extension
26
+ * `.pi/extensions/rtk.ts`. Без установленого rtk hooks мовчки пропускають команди,
27
+ * тож конфіг безпечно потрапляє в git — установка зводиться до `brew install rtk-ai/tap/rtk`.
22
28
  * - `.gitignore` — **merge** (лише з `adr`): дописує відсутні рядки з канонічного
23
29
  * фрагмента `rules/adr/js/hooks/template/.gitignore.snippet` (`node_modules/`, `dist/`,
24
30
  * `*.secret`, логи capture/normalize, `.normalize-state`, `.normalize.lock`,
@@ -52,6 +58,10 @@ export const ADR_NORMALIZE_HOOK_COMMAND_MARKER = '.claude/hooks/normalize-decisi
52
58
  export const CURSOR_ADR_HOOK_COMMAND_MARKER = '.claude/hooks/capture-decisions.sh'
53
59
  /** Маркер Cursor ADR Normalize Stop-hook'а — той самий script path, але в `.cursor/hooks.json`. */
54
60
  export const CURSOR_ADR_NORMALIZE_HOOK_COMMAND_MARKER = '.claude/hooks/normalize-decisions.sh'
61
+ /** Маркер rtk PreToolUse hook'а у `.claude/settings.json` (правило `local-ai`). */
62
+ export const RTK_CLAUDE_HOOK_COMMAND_MARKER = 'rtk hook claude'
63
+ /** Маркер rtk preToolUse hook'а у `.cursor/hooks.json` (правило `local-ai`). */
64
+ export const RTK_CURSOR_HOOK_COMMAND_MARKER = 'rtk hook cursor'
55
65
  /**
56
66
  * Усі маркери managed-hook'ів пакета — за ними відрізняємо свої записи від користувацьких.
57
67
  * Legacy stop-hook включений сюди, щоб старі entries автоматично видалялись при наступному sync-у.
@@ -65,7 +75,8 @@ export const MANAGED_HOOK_COMMAND_MARKERS = Object.freeze([
65
75
  LEGACY_DOC_FILES_HOOK_COMMAND_MARKER,
66
76
  LEGACY_STOP_HOOK_COMMAND_MARKER,
67
77
  ADR_HOOK_COMMAND_MARKER,
68
- ADR_NORMALIZE_HOOK_COMMAND_MARKER
78
+ ADR_NORMALIZE_HOOK_COMMAND_MARKER,
79
+ RTK_CLAUDE_HOOK_COMMAND_MARKER
69
80
  ])
70
81
 
71
82
  const CLAUDE_DIR = '.claude'
@@ -130,6 +141,32 @@ const CURSOR_ADR_STOP_HOOK = Object.freeze({
130
141
  timeout: 180
131
142
  })
132
143
 
144
+ /**
145
+ * Канонічна група hooks для rtk PreToolUse (правило `local-ai`): переписує Bash-команди
146
+ * на rtk-еквіваленти. Fail-open guard: без установленого rtk — `exit 0` без виводу,
147
+ * Claude Code виконує команду без змін.
148
+ */
149
+ const RTK_PRE_TOOL_USE_HOOK_GROUP = Object.freeze({
150
+ matcher: 'Bash',
151
+ hooks: Object.freeze([
152
+ Object.freeze({
153
+ type: 'command',
154
+ command: `command -v rtk >/dev/null 2>&1 && exec ${RTK_CLAUDE_HOOK_COMMAND_MARKER}; exit 0`,
155
+ timeout: 30
156
+ })
157
+ ])
158
+ })
159
+
160
+ /**
161
+ * Канонічний Cursor preToolUse hook для rtk (правило `local-ai`). Cursor вимагає JSON-вивід
162
+ * на всіх code path — без установленого rtk відповідаємо `{}` (passthrough).
163
+ */
164
+ const CURSOR_RTK_PRE_TOOL_USE_HOOK = Object.freeze({
165
+ command: `bash -lc 'command -v rtk >/dev/null 2>&1 && exec ${RTK_CURSOR_HOOK_COMMAND_MARKER}; echo "{}"'`,
166
+ matcher: 'Shell',
167
+ timeout: 30
168
+ })
169
+
133
170
  /** Канонічний Cursor stop-hook для ADR normalize. */
134
171
  const CURSOR_ADR_NORMALIZE_STOP_HOOK = Object.freeze({
135
172
  command: [
@@ -188,13 +225,13 @@ function isManagedHookGroup(group) {
188
225
  /**
189
226
  * Чи Cursor hook entry належить пакету `@7n/rules`.
190
227
  * @param {CursorHookEntry} entry один entry з `.cursor/hooks.json`
191
- * @returns {boolean} `true`, якщо command містить managed ADR marker
228
+ * @returns {boolean} `true`, якщо command містить managed ADR або rtk marker
192
229
  */
193
230
  function isManagedCursorHookEntry(entry) {
194
231
  return (
195
232
  typeof entry?.command === 'string' &&
196
- [CURSOR_ADR_HOOK_COMMAND_MARKER, CURSOR_ADR_NORMALIZE_HOOK_COMMAND_MARKER].some(marker =>
197
- entry.command.includes(marker)
233
+ [CURSOR_ADR_HOOK_COMMAND_MARKER, CURSOR_ADR_NORMALIZE_HOOK_COMMAND_MARKER, RTK_CURSOR_HOOK_COMMAND_MARKER].some(
234
+ marker => entry.command.includes(marker)
198
235
  )
199
236
  )
200
237
  }
@@ -268,16 +305,36 @@ function templateWithAdrHook(template) {
268
305
  return { ...template, hooks }
269
306
  }
270
307
 
308
+ /**
309
+ * Будує копію темплейту із додатковою rtk PreToolUse hook-групою (правило `local-ai`).
310
+ * Темплейт залишається незмінним; повертається новий об'єкт з доданою групою.
311
+ * @param {ClaudeSettings} template вихідний темплейт із `.claude-template/settings.template.json`
312
+ * @returns {ClaudeSettings} копія з доданою rtk-групою у `hooks.PreToolUse`
313
+ */
314
+ function templateWithRtkHook(template) {
315
+ /** @type {Record<string, HookGroup[]>} */
316
+ const hooks = {}
317
+ for (const [event, groups] of Object.entries(template.hooks ?? {})) {
318
+ hooks[event] = Array.isArray(groups) ? [...groups] : []
319
+ }
320
+ hooks.PreToolUse = [...(hooks.PreToolUse ?? []), /** @type {HookGroup} */ (RTK_PRE_TOOL_USE_HOOK_GROUP)]
321
+ return { ...template, hooks }
322
+ }
323
+
271
324
  /**
272
325
  * Повертає об'єднаний об'єкт settings.json.
273
326
  * @param {ClaudeSettings | undefined} existing існуючий вміст `.claude/settings.json` користувача (або undefined, якщо файла нема)
274
327
  * @param {ClaudeSettings} template settings із темплейту пакета `@7n/rules`
275
328
  * @param {object} [options] опції merge-у
276
329
  * @param {boolean} [options.includeAdrHook] чи додати ADR Stop-hook групу до managed-hooks (коли в `.n-rules.json` `rules` присутнє `adr`)
330
+ * @param {boolean} [options.includeLocalAiHook] чи додати rtk PreToolUse-групу (коли в `.n-rules.json` `rules` присутнє `local-ai`)
277
331
  * @returns {ClaudeSettings} результат merge-у (користувацькі поля збережено, наші перевизначено)
278
332
  */
279
333
  export function mergeSettings(existing, template, options = {}) {
280
- const effectiveTemplate = options.includeAdrHook ? templateWithAdrHook(template) : template
334
+ let effectiveTemplate = options.includeAdrHook ? templateWithAdrHook(template) : template
335
+ if (options.includeLocalAiHook) {
336
+ effectiveTemplate = templateWithRtkHook(effectiveTemplate)
337
+ }
281
338
  /** @type {ClaudeSettings} */
282
339
  const merged = { ...existing }
283
340
  const mergedAllow = mergeAllowList(existing?.permissions?.allow, effectiveTemplate.permissions?.allow)
@@ -295,10 +352,12 @@ export function mergeSettings(existing, template, options = {}) {
295
352
 
296
353
  /**
297
354
  * Зливає `.cursor/hooks.json`: користувацькі entries зберігаються, managed ADR
298
- * entries у `hooks.stop` перезаписуються або видаляються залежно від `includeAdrHook`.
355
+ * entries у `hooks.stop` перезаписуються або видаляються залежно від `includeAdrHook`;
356
+ * managed rtk entry у `hooks.preToolUse` — залежно від `includeLocalAiHook`.
299
357
  * @param {CursorHooksConfig | undefined} existing поточний Cursor hooks config
300
358
  * @param {object} [options] опції merge-у
301
359
  * @param {boolean} [options.includeAdrHook] чи додати ADR stop entries
360
+ * @param {boolean} [options.includeLocalAiHook] чи додати rtk preToolUse entry
302
361
  * @returns {CursorHooksConfig} результат злиття
303
362
  */
304
363
  export function mergeCursorHooksConfig(existing, options = {}) {
@@ -321,6 +380,15 @@ export function mergeCursorHooksConfig(existing, options = {}) {
321
380
  } else {
322
381
  delete hooks.stop
323
382
  }
383
+ const preToolUse = (hooks.preToolUse ?? []).filter(entry => !isManagedCursorHookEntry(entry))
384
+ if (options.includeLocalAiHook) {
385
+ preToolUse.push(/** @type {CursorHookEntry} */ (CURSOR_RTK_PRE_TOOL_USE_HOOK))
386
+ }
387
+ if (preToolUse.length > 0) {
388
+ hooks.preToolUse = preToolUse
389
+ } else {
390
+ delete hooks.preToolUse
391
+ }
324
392
  merged.version = typeof merged.version === 'number' ? merged.version : 1
325
393
  if (Object.keys(hooks).length > 0) {
326
394
  merged.hooks = hooks
@@ -347,17 +415,18 @@ async function readJsonOrUndefined(path) {
347
415
  }
348
416
 
349
417
  /**
350
- * Синхронізує `.cursor/hooks.json` для Cursor Agent stop-hooks. Cursor читає
351
- * project-level config з `.cursor/hooks.json`; hook scripts лишаються спільними
352
- * з Claude Code у `.claude/hooks/`.
418
+ * Синхронізує `.cursor/hooks.json` для Cursor Agent hooks (ADR stop + rtk preToolUse).
419
+ * Cursor читає project-level config з `.cursor/hooks.json`; hook scripts лишаються
420
+ * спільними з Claude Code у `.claude/hooks/`.
353
421
  * @param {string} projectRoot корінь проєкту, куди писати
354
422
  * @param {object} [options] опції merge-у
355
423
  * @param {boolean} [options.includeAdrHook] чи додавати ADR stop-hook entries
424
+ * @param {boolean} [options.includeLocalAiHook] чи додавати rtk preToolUse entry
356
425
  * @returns {Promise<{ written: boolean, path: string }>} результат: чи писали файл, та його відносний шлях
357
426
  */
358
427
  export async function syncCursorHooksConfig(projectRoot, options = {}) {
359
428
  const hooksPath = join(projectRoot, CURSOR_HOOKS_FILE)
360
- if (!options.includeAdrHook && !existsSync(hooksPath)) {
429
+ if (!options.includeAdrHook && !options.includeLocalAiHook && !existsSync(hooksPath)) {
361
430
  return { written: false, path: '' }
362
431
  }
363
432
  const existing = /** @type {CursorHooksConfig | undefined} */ (await readJsonOrUndefined(hooksPath))
@@ -539,6 +608,44 @@ export async function removeOrphanPiExtension(projectRoot) {
539
608
  return { removed: true, path: `${PI_EXTENSIONS_DIR}/${PI_EXTENSION_NAME}` }
540
609
  }
541
610
 
611
+ /** Ім'я файлу rtk pi-extension — той самий шлях, що пише `rtk init --agent pi` (повторна установка ідемпотентна). */
612
+ export const RTK_PI_EXTENSION_FILE = 'rtk.ts'
613
+
614
+ /**
615
+ * Копіює vendored rtk pi-extension `npm/.pi-template/extensions/rtk.ts` у
616
+ * `.pi/extensions/rtk.ts` проєкту-споживача (правило `local-ai`). Файл fully-owned:
617
+ * при кожному sync-у перезаписується. Якщо bundled template відсутній — `{written: false}`.
618
+ * @param {string} projectRoot корінь проєкту-споживача
619
+ * @param {string} bundledPackageRoot корінь установленого `@7n/rules` (із `.pi-template/`)
620
+ * @returns {Promise<{ written: boolean, path: string }>} чи писали та відносний шлях файла
621
+ */
622
+ export async function syncRtkPiExtension(projectRoot, bundledPackageRoot) {
623
+ const srcPath = join(bundledPackageRoot, PI_TEMPLATE_DIR_NAME, 'extensions', RTK_PI_EXTENSION_FILE)
624
+ if (!existsSync(srcPath)) {
625
+ return { written: false, path: '' }
626
+ }
627
+ const destDir = join(projectRoot, PI_EXTENSIONS_DIR)
628
+ await mkdir(destDir, { recursive: true })
629
+ await writeFile(join(destDir, RTK_PI_EXTENSION_FILE), await readFile(srcPath, 'utf8'), 'utf8')
630
+ return { written: true, path: `${PI_EXTENSIONS_DIR}/${RTK_PI_EXTENSION_FILE}` }
631
+ }
632
+
633
+ /**
634
+ * Видаляє `.pi/extensions/rtk.ts` з проєкту-споживача. Викликається, коли правило
635
+ * `local-ai` вимкнено у `.n-rules.json` (симетрично до cleanup-у hook-записів).
636
+ * Шлях спільний з `rtk init --agent pi`, тож прибереться і встановлений вручну файл.
637
+ * @param {string} projectRoot корінь проєкту-споживача
638
+ * @returns {Promise<{ removed: boolean, path: string }>} чи було щось видалено та відносний шлях
639
+ */
640
+ export async function removeOrphanRtkPiExtension(projectRoot) {
641
+ const extPath = join(projectRoot, PI_EXTENSIONS_DIR, RTK_PI_EXTENSION_FILE)
642
+ if (!existsSync(extPath)) {
643
+ return { removed: false, path: '' }
644
+ }
645
+ await rm(extPath, { force: true })
646
+ return { removed: true, path: `${PI_EXTENSIONS_DIR}/${RTK_PI_EXTENSION_FILE}` }
647
+ }
648
+
542
649
  /**
543
650
  * Повертає змістовні (не коментар, не порожній) рядки з text-фрагмента `.gitignore`.
544
651
  * @param {string} raw вміст snippet-файлу
@@ -627,36 +734,30 @@ export async function syncClaudeCommands(projectRoot, templateDir) {
627
734
  * @param {string} options.projectRoot корінь проєкту-споживача
628
735
  * @param {string} options.bundledPackageRoot корінь установленого `@7n/rules`
629
736
  * @param {boolean} options.enabled чи увімкнено sync (з `.n-rules.json` `claude-config`)
630
- * @param {string[]} [options.rules] список увімкнених правил із `.n-rules.json` — впливає на ADR Stop-hook (`adr`)
631
- * @returns {Promise<{ settings: boolean, cursorHooks: boolean, commands: string[], adrHook: boolean, adrNormalizeHook: boolean, adrHookLib: string[], gitignoreAdr: boolean, piExtension: boolean }>} прапорці записів settings/Cursor hooks/ADR-hook(s)/`.gitignore`/pi-extension, перелік lib-файлів і список slash-команд
737
+ * @param {string[]} [options.rules] список увімкнених правил із `.n-rules.json` — впливає на ADR Stop-hook (`adr`) і rtk hooks (`local-ai`)
738
+ * @returns {Promise<{ settings: boolean, cursorHooks: boolean, commands: string[], adrHook: boolean, adrNormalizeHook: boolean, adrHookLib: string[], gitignoreAdr: boolean, piExtension: boolean, rtkPiExtension: boolean }>} прапорці записів settings/Cursor hooks/ADR-hook(s)/`.gitignore`/pi-extension(s), перелік lib-файлів і список slash-команд
632
739
  */
633
740
  export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enabled, rules = [] }) {
741
+ const noop = {
742
+ settings: false,
743
+ cursorHooks: false,
744
+ commands: [],
745
+ adrHook: false,
746
+ adrNormalizeHook: false,
747
+ adrHookLib: [],
748
+ gitignoreAdr: false,
749
+ piExtension: false,
750
+ rtkPiExtension: false
751
+ }
634
752
  if (!enabled) {
635
- return {
636
- settings: false,
637
- cursorHooks: false,
638
- commands: [],
639
- adrHook: false,
640
- adrNormalizeHook: false,
641
- adrHookLib: [],
642
- gitignoreAdr: false,
643
- piExtension: false
644
- }
753
+ return noop
645
754
  }
646
755
  const templateDir = join(bundledPackageRoot, TEMPLATE_DIR_NAME)
647
756
  if (!existsSync(templateDir)) {
648
- return {
649
- settings: false,
650
- cursorHooks: false,
651
- commands: [],
652
- adrHook: false,
653
- adrNormalizeHook: false,
654
- adrHookLib: [],
655
- gitignoreAdr: false,
656
- piExtension: false
657
- }
757
+ return noop
658
758
  }
659
759
  const includeAdrHook = Array.isArray(rules) && rules.includes('adr')
760
+ const includeLocalAiHook = Array.isArray(rules) && rules.includes('local-ai')
660
761
  const adrHook = includeAdrHook ? await syncAdrHookScript(projectRoot, templateDir) : { written: false, path: '' }
661
762
  const adrNormalizeHook = includeAdrHook
662
763
  ? await syncAdrNormalizeHookScript(projectRoot, templateDir)
@@ -676,8 +777,15 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
676
777
  const removed = await removeOrphanPiExtension(projectRoot)
677
778
  piExtension = { written: false, path: removed.path }
678
779
  }
679
- const settings = await syncClaudeSettings(projectRoot, templateDir, { includeAdrHook })
680
- const cursorHooks = await syncCursorHooksConfig(projectRoot, { includeAdrHook })
780
+ let rtkPiExtension
781
+ if (includeLocalAiHook) {
782
+ rtkPiExtension = await syncRtkPiExtension(projectRoot, bundledPackageRoot)
783
+ } else {
784
+ const removed = await removeOrphanRtkPiExtension(projectRoot)
785
+ rtkPiExtension = { written: false, path: removed.path }
786
+ }
787
+ const settings = await syncClaudeSettings(projectRoot, templateDir, { includeAdrHook, includeLocalAiHook })
788
+ const cursorHooks = await syncCursorHooksConfig(projectRoot, { includeAdrHook, includeLocalAiHook })
681
789
  const commands = await syncClaudeCommands(projectRoot, templateDir)
682
790
  return {
683
791
  settings: settings.written,
@@ -687,6 +795,7 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
687
795
  adrNormalizeHook: adrNormalizeHook.written,
688
796
  adrHookLib: adrHookLibEntries.map(e => e.path),
689
797
  gitignoreAdr: gitignoreAdr.written,
690
- piExtension: piExtension.written
798
+ piExtension: piExtension.written,
799
+ rtkPiExtension: rtkPiExtension.written
691
800
  }
692
801
  }