colyn-cli 3.1.4

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 (263) hide show
  1. package/README-en.md +85 -0
  2. package/README.md +85 -0
  3. package/bin/colyn +25 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +116 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/add.d.ts +6 -0
  9. package/dist/commands/add.d.ts.map +1 -0
  10. package/dist/commands/add.helpers.d.ts +48 -0
  11. package/dist/commands/add.helpers.d.ts.map +1 -0
  12. package/dist/commands/add.helpers.js +251 -0
  13. package/dist/commands/add.helpers.js.map +1 -0
  14. package/dist/commands/add.js +286 -0
  15. package/dist/commands/add.js.map +1 -0
  16. package/dist/commands/checkout.d.ts +16 -0
  17. package/dist/commands/checkout.d.ts.map +1 -0
  18. package/dist/commands/checkout.js +428 -0
  19. package/dist/commands/checkout.js.map +1 -0
  20. package/dist/commands/completion.d.ts +10 -0
  21. package/dist/commands/completion.d.ts.map +1 -0
  22. package/dist/commands/completion.js +380 -0
  23. package/dist/commands/completion.js.map +1 -0
  24. package/dist/commands/config.d.ts +11 -0
  25. package/dist/commands/config.d.ts.map +1 -0
  26. package/dist/commands/config.js +338 -0
  27. package/dist/commands/config.js.map +1 -0
  28. package/dist/commands/index.d.ts +10 -0
  29. package/dist/commands/index.d.ts.map +1 -0
  30. package/dist/commands/index.js +41 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/info.d.ts +6 -0
  33. package/dist/commands/info.d.ts.map +1 -0
  34. package/dist/commands/info.js +374 -0
  35. package/dist/commands/info.js.map +1 -0
  36. package/dist/commands/init.d.ts +6 -0
  37. package/dist/commands/init.d.ts.map +1 -0
  38. package/dist/commands/init.handlers.d.ts +27 -0
  39. package/dist/commands/init.handlers.d.ts.map +1 -0
  40. package/dist/commands/init.handlers.js +314 -0
  41. package/dist/commands/init.handlers.js.map +1 -0
  42. package/dist/commands/init.helpers.d.ts +42 -0
  43. package/dist/commands/init.helpers.d.ts.map +1 -0
  44. package/dist/commands/init.helpers.js +275 -0
  45. package/dist/commands/init.helpers.js.map +1 -0
  46. package/dist/commands/init.js +61 -0
  47. package/dist/commands/init.js.map +1 -0
  48. package/dist/commands/install.d.ts +6 -0
  49. package/dist/commands/install.d.ts.map +1 -0
  50. package/dist/commands/install.helpers.d.ts +32 -0
  51. package/dist/commands/install.helpers.d.ts.map +1 -0
  52. package/dist/commands/install.helpers.js +124 -0
  53. package/dist/commands/install.helpers.js.map +1 -0
  54. package/dist/commands/install.js +104 -0
  55. package/dist/commands/install.js.map +1 -0
  56. package/dist/commands/list-project.d.ts +11 -0
  57. package/dist/commands/list-project.d.ts.map +1 -0
  58. package/dist/commands/list-project.js +260 -0
  59. package/dist/commands/list-project.js.map +1 -0
  60. package/dist/commands/list.d.ts +15 -0
  61. package/dist/commands/list.d.ts.map +1 -0
  62. package/dist/commands/list.helpers.d.ts +50 -0
  63. package/dist/commands/list.helpers.d.ts.map +1 -0
  64. package/dist/commands/list.helpers.js +143 -0
  65. package/dist/commands/list.helpers.js.map +1 -0
  66. package/dist/commands/list.js +530 -0
  67. package/dist/commands/list.js.map +1 -0
  68. package/dist/commands/merge.d.ts +6 -0
  69. package/dist/commands/merge.d.ts.map +1 -0
  70. package/dist/commands/merge.helpers.d.ts +74 -0
  71. package/dist/commands/merge.helpers.d.ts.map +1 -0
  72. package/dist/commands/merge.helpers.js +307 -0
  73. package/dist/commands/merge.helpers.js.map +1 -0
  74. package/dist/commands/merge.js +260 -0
  75. package/dist/commands/merge.js.map +1 -0
  76. package/dist/commands/release.d.ts +6 -0
  77. package/dist/commands/release.d.ts.map +1 -0
  78. package/dist/commands/release.helpers.d.ts +61 -0
  79. package/dist/commands/release.helpers.d.ts.map +1 -0
  80. package/dist/commands/release.helpers.js +277 -0
  81. package/dist/commands/release.helpers.js.map +1 -0
  82. package/dist/commands/release.js +127 -0
  83. package/dist/commands/release.js.map +1 -0
  84. package/dist/commands/remove.d.ts +6 -0
  85. package/dist/commands/remove.d.ts.map +1 -0
  86. package/dist/commands/remove.helpers.d.ts +59 -0
  87. package/dist/commands/remove.helpers.d.ts.map +1 -0
  88. package/dist/commands/remove.helpers.js +190 -0
  89. package/dist/commands/remove.helpers.js.map +1 -0
  90. package/dist/commands/remove.js +137 -0
  91. package/dist/commands/remove.js.map +1 -0
  92. package/dist/commands/repair.d.ts +6 -0
  93. package/dist/commands/repair.d.ts.map +1 -0
  94. package/dist/commands/repair.helpers.d.ts +5 -0
  95. package/dist/commands/repair.helpers.d.ts.map +1 -0
  96. package/dist/commands/repair.helpers.js +499 -0
  97. package/dist/commands/repair.helpers.js.map +1 -0
  98. package/dist/commands/repair.js +28 -0
  99. package/dist/commands/repair.js.map +1 -0
  100. package/dist/commands/status.d.ts +6 -0
  101. package/dist/commands/status.d.ts.map +1 -0
  102. package/dist/commands/status.js +116 -0
  103. package/dist/commands/status.js.map +1 -0
  104. package/dist/commands/system-integration.d.ts +6 -0
  105. package/dist/commands/system-integration.d.ts.map +1 -0
  106. package/dist/commands/system-integration.helpers.d.ts +70 -0
  107. package/dist/commands/system-integration.helpers.d.ts.map +1 -0
  108. package/dist/commands/system-integration.helpers.js +313 -0
  109. package/dist/commands/system-integration.helpers.js.map +1 -0
  110. package/dist/commands/system-integration.js +132 -0
  111. package/dist/commands/system-integration.js.map +1 -0
  112. package/dist/commands/tmux.d.ts +11 -0
  113. package/dist/commands/tmux.d.ts.map +1 -0
  114. package/dist/commands/tmux.js +500 -0
  115. package/dist/commands/tmux.js.map +1 -0
  116. package/dist/commands/todo.d.ts +6 -0
  117. package/dist/commands/todo.d.ts.map +1 -0
  118. package/dist/commands/todo.helpers.d.ts +50 -0
  119. package/dist/commands/todo.helpers.d.ts.map +1 -0
  120. package/dist/commands/todo.helpers.js +297 -0
  121. package/dist/commands/todo.helpers.js.map +1 -0
  122. package/dist/commands/todo.js +579 -0
  123. package/dist/commands/todo.js.map +1 -0
  124. package/dist/commands/update.d.ts +18 -0
  125. package/dist/commands/update.d.ts.map +1 -0
  126. package/dist/commands/update.helpers.d.ts +89 -0
  127. package/dist/commands/update.helpers.d.ts.map +1 -0
  128. package/dist/commands/update.helpers.js +335 -0
  129. package/dist/commands/update.helpers.js.map +1 -0
  130. package/dist/commands/update.js +187 -0
  131. package/dist/commands/update.js.map +1 -0
  132. package/dist/core/config-loader.d.ts +47 -0
  133. package/dist/core/config-loader.d.ts.map +1 -0
  134. package/dist/core/config-loader.js +188 -0
  135. package/dist/core/config-loader.js.map +1 -0
  136. package/dist/core/config-merger.d.ts +29 -0
  137. package/dist/core/config-merger.d.ts.map +1 -0
  138. package/dist/core/config-merger.js +100 -0
  139. package/dist/core/config-merger.js.map +1 -0
  140. package/dist/core/config-migration.d.ts +64 -0
  141. package/dist/core/config-migration.d.ts.map +1 -0
  142. package/dist/core/config-migration.js +340 -0
  143. package/dist/core/config-migration.js.map +1 -0
  144. package/dist/core/config-new.d.ts +19 -0
  145. package/dist/core/config-new.d.ts.map +1 -0
  146. package/dist/core/config-new.js +58 -0
  147. package/dist/core/config-new.js.map +1 -0
  148. package/dist/core/config-schema.d.ts +221 -0
  149. package/dist/core/config-schema.d.ts.map +1 -0
  150. package/dist/core/config-schema.js +168 -0
  151. package/dist/core/config-schema.js.map +1 -0
  152. package/dist/core/config.d.ts +55 -0
  153. package/dist/core/config.d.ts.map +1 -0
  154. package/dist/core/config.js +143 -0
  155. package/dist/core/config.js.map +1 -0
  156. package/dist/core/dev-server.d.ts +29 -0
  157. package/dist/core/dev-server.d.ts.map +1 -0
  158. package/dist/core/dev-server.js +54 -0
  159. package/dist/core/dev-server.js.map +1 -0
  160. package/dist/core/discovery.d.ts +51 -0
  161. package/dist/core/discovery.d.ts.map +1 -0
  162. package/dist/core/discovery.js +247 -0
  163. package/dist/core/discovery.js.map +1 -0
  164. package/dist/core/env.d.ts +13 -0
  165. package/dist/core/env.d.ts.map +1 -0
  166. package/dist/core/env.js +75 -0
  167. package/dist/core/env.js.map +1 -0
  168. package/dist/core/git.d.ts +31 -0
  169. package/dist/core/git.d.ts.map +1 -0
  170. package/dist/core/git.js +56 -0
  171. package/dist/core/git.js.map +1 -0
  172. package/dist/core/paths.d.ts +86 -0
  173. package/dist/core/paths.d.ts.map +1 -0
  174. package/dist/core/paths.js +256 -0
  175. package/dist/core/paths.js.map +1 -0
  176. package/dist/core/tmux-config.d.ts +174 -0
  177. package/dist/core/tmux-config.d.ts.map +1 -0
  178. package/dist/core/tmux-config.js +545 -0
  179. package/dist/core/tmux-config.js.map +1 -0
  180. package/dist/core/tmux-config.legacy.d.ts +243 -0
  181. package/dist/core/tmux-config.legacy.d.ts.map +1 -0
  182. package/dist/core/tmux-config.legacy.js +896 -0
  183. package/dist/core/tmux-config.legacy.js.map +1 -0
  184. package/dist/core/tmux.d.ts +214 -0
  185. package/dist/core/tmux.d.ts.map +1 -0
  186. package/dist/core/tmux.js +712 -0
  187. package/dist/core/tmux.js.map +1 -0
  188. package/dist/core/toolchain-resolver.d.ts +51 -0
  189. package/dist/core/toolchain-resolver.d.ts.map +1 -0
  190. package/dist/core/toolchain-resolver.js +364 -0
  191. package/dist/core/toolchain-resolver.js.map +1 -0
  192. package/dist/core/worktree-status.d.ts +20 -0
  193. package/dist/core/worktree-status.d.ts.map +1 -0
  194. package/dist/core/worktree-status.js +67 -0
  195. package/dist/core/worktree-status.js.map +1 -0
  196. package/dist/i18n/index.d.ts +36 -0
  197. package/dist/i18n/index.d.ts.map +1 -0
  198. package/dist/i18n/index.js +157 -0
  199. package/dist/i18n/index.js.map +1 -0
  200. package/dist/i18n/locales/en.d.ts +866 -0
  201. package/dist/i18n/locales/en.d.ts.map +1 -0
  202. package/dist/i18n/locales/en.js +985 -0
  203. package/dist/i18n/locales/en.js.map +1 -0
  204. package/dist/i18n/locales/zh-CN.d.ts +865 -0
  205. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  206. package/dist/i18n/locales/zh-CN.js +985 -0
  207. package/dist/i18n/locales/zh-CN.js.map +1 -0
  208. package/dist/index.d.ts +2 -0
  209. package/dist/index.d.ts.map +1 -0
  210. package/dist/index.js +4 -0
  211. package/dist/index.js.map +1 -0
  212. package/dist/plugins/builtin/gradle.d.ts +9 -0
  213. package/dist/plugins/builtin/gradle.d.ts.map +1 -0
  214. package/dist/plugins/builtin/gradle.js +164 -0
  215. package/dist/plugins/builtin/gradle.js.map +1 -0
  216. package/dist/plugins/builtin/maven.d.ts +9 -0
  217. package/dist/plugins/builtin/maven.d.ts.map +1 -0
  218. package/dist/plugins/builtin/maven.js +127 -0
  219. package/dist/plugins/builtin/maven.js.map +1 -0
  220. package/dist/plugins/builtin/npm.d.ts +9 -0
  221. package/dist/plugins/builtin/npm.d.ts.map +1 -0
  222. package/dist/plugins/builtin/npm.js +238 -0
  223. package/dist/plugins/builtin/npm.js.map +1 -0
  224. package/dist/plugins/builtin/pip.d.ts +9 -0
  225. package/dist/plugins/builtin/pip.d.ts.map +1 -0
  226. package/dist/plugins/builtin/pip.js +210 -0
  227. package/dist/plugins/builtin/pip.js.map +1 -0
  228. package/dist/plugins/builtin/xcode.d.ts +12 -0
  229. package/dist/plugins/builtin/xcode.d.ts.map +1 -0
  230. package/dist/plugins/builtin/xcode.js +438 -0
  231. package/dist/plugins/builtin/xcode.js.map +1 -0
  232. package/dist/plugins/index.d.ts +13 -0
  233. package/dist/plugins/index.d.ts.map +1 -0
  234. package/dist/plugins/index.js +24 -0
  235. package/dist/plugins/index.js.map +1 -0
  236. package/dist/plugins/manager.d.ts +93 -0
  237. package/dist/plugins/manager.d.ts.map +1 -0
  238. package/dist/plugins/manager.js +270 -0
  239. package/dist/plugins/manager.js.map +1 -0
  240. package/dist/plugins/utils.d.ts +42 -0
  241. package/dist/plugins/utils.d.ts.map +1 -0
  242. package/dist/plugins/utils.js +175 -0
  243. package/dist/plugins/utils.js.map +1 -0
  244. package/dist/types/index.d.ts +104 -0
  245. package/dist/types/index.d.ts.map +1 -0
  246. package/dist/types/index.js +21 -0
  247. package/dist/types/index.js.map +1 -0
  248. package/dist/types/plugin.d.ts +200 -0
  249. package/dist/types/plugin.d.ts.map +1 -0
  250. package/dist/types/plugin.js +19 -0
  251. package/dist/types/plugin.js.map +1 -0
  252. package/dist/utils/logger.d.ts +42 -0
  253. package/dist/utils/logger.d.ts.map +1 -0
  254. package/dist/utils/logger.js +80 -0
  255. package/dist/utils/logger.js.map +1 -0
  256. package/docs/en/manual/04-command-reference/README.md +58 -0
  257. package/docs/en/manual/README.md +108 -0
  258. package/docs/zh-CN/manual/04-command-reference/README.md +58 -0
  259. package/docs/zh-CN/manual/README.md +108 -0
  260. package/package.json +65 -0
  261. package/shell/colyn.sh +55 -0
  262. package/shell/completion.bash +270 -0
  263. package/shell/completion.zsh +167 -0
@@ -0,0 +1,896 @@
1
+ /**
2
+ * Tmux Pane 命令配置模块
3
+ *
4
+ * 提供 tmux pane 命令的配置加载和自动检测功能
5
+ *
6
+ * 配置文件层级(优先级从低到高):
7
+ * 1. 内置默认值
8
+ * 2. 用户级配置:~/.config/colyn/settings.json
9
+ * 3. 项目级配置:{projectRoot}/.colyn/settings.json
10
+ */
11
+ import * as fs from 'fs/promises';
12
+ import * as path from 'path';
13
+ import * as os from 'os';
14
+ import { getDevServerCommand } from './dev-server.js';
15
+ /**
16
+ * 内置命令常量
17
+ */
18
+ export const BUILTIN_COMMANDS = {
19
+ /** 启动 dev server */
20
+ AUTO_DEV_SERVER: 'start dev server',
21
+ /** 继续 Claude 会话 */
22
+ AUTO_CLAUDE: 'continue claude session',
23
+ };
24
+ /**
25
+ * 窗格名称映射表
26
+ * 定义每种布局支持的窗格名称
27
+ */
28
+ export const LAYOUT_PANES = {
29
+ 'single-pane': [],
30
+ 'two-pane-horizontal': ['leftPane', 'rightPane'],
31
+ 'two-pane-vertical': ['topPane', 'bottomPane'],
32
+ 'three-pane': ['leftPane', 'topRightPane', 'bottomRightPane'],
33
+ 'four-pane': ['topLeftPane', 'topRightPane', 'bottomLeftPane', 'bottomRightPane'],
34
+ };
35
+ /**
36
+ * 当前配置文件版本号
37
+ */
38
+ export const CURRENT_CONFIG_VERSION = 3;
39
+ /**
40
+ * 配置文件路径
41
+ */
42
+ const SETTINGS_FILENAME = 'settings.json';
43
+ const CONFIG_DIR = '.colyn';
44
+ /**
45
+ * 默认配置(三窗格布局)
46
+ */
47
+ const DEFAULT_CONFIG = {
48
+ autoRun: true,
49
+ layout: 'three-pane',
50
+ leftPane: {
51
+ command: BUILTIN_COMMANDS.AUTO_CLAUDE,
52
+ size: '60%',
53
+ },
54
+ topRightPane: {
55
+ command: BUILTIN_COMMANDS.AUTO_DEV_SERVER,
56
+ size: '30%',
57
+ },
58
+ bottomRightPane: {
59
+ command: null,
60
+ size: '70%',
61
+ },
62
+ };
63
+ /**
64
+ * 系统内置的分支特定默认配置
65
+ * 优先级最低,会被任何用户或项目配置覆盖
66
+ */
67
+ const BUILTIN_BRANCH_DEFAULTS = {
68
+ // Main 分支默认使用单窗格布局
69
+ main: {
70
+ layout: 'single-pane',
71
+ autoRun: false,
72
+ },
73
+ };
74
+ /**
75
+ * 获取用户级配置目录
76
+ * 遵循 XDG Base Directory 规范
77
+ */
78
+ export function getUserConfigDir() {
79
+ return path.join(os.homedir(), '.config', 'colyn');
80
+ }
81
+ /**
82
+ * 获取用户级设置文件路径
83
+ */
84
+ export function getUserConfigPath() {
85
+ return path.join(getUserConfigDir(), SETTINGS_FILENAME);
86
+ }
87
+ /**
88
+ * 获取项目级设置文件路径
89
+ * @param projectRoot 项目根目录
90
+ */
91
+ export function getProjectConfigPath(projectRoot) {
92
+ return path.join(projectRoot, CONFIG_DIR, SETTINGS_FILENAME);
93
+ }
94
+ /**
95
+ * 从设置文件加载 tmux 配置
96
+ * @param configPath 设置文件路径
97
+ * @returns tmux 配置对象,如果文件不存在或无法解析则返回 null
98
+ */
99
+ async function loadConfigFromFile(configPath) {
100
+ try {
101
+ const content = await fs.readFile(configPath, 'utf-8');
102
+ const settings = JSON.parse(content);
103
+ return settings.tmux ?? null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ /**
110
+ * Migration 函数列表
111
+ * 索引 i 的函数负责从 version i 迁移到 version i+1
112
+ */
113
+ /**
114
+ * 废弃的内置命令常量
115
+ */
116
+ const DEPRECATED_BUILTIN_COMMANDS = {
117
+ AUTO_CLAUDE_DANGEROUSLY: 'auto continues claude session with dangerously skip permissions',
118
+ AUTO_CLAUDE_OLD: 'auto continues claude session',
119
+ AUTO_DEV_SERVER_OLD: 'auto start dev server',
120
+ };
121
+ const MIGRATIONS = [
122
+ // Migration 0 -> 1: 添加版本号
123
+ (settings) => {
124
+ // 版本 0(无版本号)-> 版本 1
125
+ // 这是第一次引入版本号,无需修改其他字段
126
+ return {
127
+ ...settings,
128
+ version: 1,
129
+ };
130
+ },
131
+ // Migration 1 -> 2: 迁移配置结构和废弃的内置命令
132
+ (settings) => {
133
+ const migrateSettings = (s) => {
134
+ const result = { ...s };
135
+ // 1. 迁移 npm 和 claudeCommand 到 systemCommands
136
+ const hasOldNpm = 'npm' in s && s.npm !== undefined;
137
+ const hasOldClaudeCommand = 'claudeCommand' in s && s.claudeCommand !== undefined;
138
+ if (hasOldNpm || hasOldClaudeCommand) {
139
+ result.systemCommands = {
140
+ ...result.systemCommands,
141
+ };
142
+ if (hasOldNpm) {
143
+ result.systemCommands.npm = s.npm;
144
+ delete result.npm;
145
+ }
146
+ if (hasOldClaudeCommand) {
147
+ result.systemCommands.claude = s.claudeCommand;
148
+ delete result.claudeCommand;
149
+ }
150
+ }
151
+ // 2. 迁移废弃的内置命令:auto continues claude session with dangerously skip permissions
152
+ if (result.tmux) {
153
+ let needsDangerouslySkipPermissions = false;
154
+ // 检查所有 pane 配置
155
+ const checkAndMigratePane = (paneConfig) => {
156
+ if (!paneConfig || !paneConfig.command) {
157
+ return paneConfig;
158
+ }
159
+ if (paneConfig.command === DEPRECATED_BUILTIN_COMMANDS.AUTO_CLAUDE_DANGEROUSLY) {
160
+ needsDangerouslySkipPermissions = true;
161
+ return {
162
+ ...paneConfig,
163
+ command: BUILTIN_COMMANDS.AUTO_CLAUDE,
164
+ };
165
+ }
166
+ return paneConfig;
167
+ };
168
+ // 迁移所有 pane 配置
169
+ const migratedTmux = { ...result.tmux };
170
+ migratedTmux.leftPane = checkAndMigratePane(migratedTmux.leftPane);
171
+ migratedTmux.rightPane = checkAndMigratePane(migratedTmux.rightPane);
172
+ migratedTmux.topPane = checkAndMigratePane(migratedTmux.topPane);
173
+ migratedTmux.bottomPane = checkAndMigratePane(migratedTmux.bottomPane);
174
+ migratedTmux.topRightPane = checkAndMigratePane(migratedTmux.topRightPane);
175
+ migratedTmux.bottomRightPane = checkAndMigratePane(migratedTmux.bottomRightPane);
176
+ migratedTmux.topLeftPane = checkAndMigratePane(migratedTmux.topLeftPane);
177
+ migratedTmux.bottomLeftPane = checkAndMigratePane(migratedTmux.bottomLeftPane);
178
+ result.tmux = migratedTmux;
179
+ // 如果检测到废弃的命令,添加 --dangerously-skip-permissions 到 systemCommands.claude
180
+ if (needsDangerouslySkipPermissions) {
181
+ result.systemCommands = result.systemCommands || {};
182
+ const currentClaudeCommand = result.systemCommands.claude || 'claude';
183
+ // 如果还没有 --dangerously-skip-permissions 参数,添加它
184
+ if (!currentClaudeCommand.includes('--dangerously-skip-permissions')) {
185
+ result.systemCommands.claude = `${currentClaudeCommand} --dangerously-skip-permissions`;
186
+ }
187
+ }
188
+ }
189
+ // 3. 递归处理 branchOverrides
190
+ if (result.branchOverrides) {
191
+ const migratedOverrides = {};
192
+ for (const [branch, branchSettings] of Object.entries(result.branchOverrides)) {
193
+ migratedOverrides[branch] = migrateSettings(branchSettings);
194
+ }
195
+ result.branchOverrides = migratedOverrides;
196
+ }
197
+ return result;
198
+ };
199
+ return {
200
+ ...migrateSettings(settings),
201
+ version: 2,
202
+ };
203
+ },
204
+ // Migration 2 -> 3: 重命名内置命令(去掉 "auto" 前缀)
205
+ (settings) => {
206
+ const migrateSettings = (s) => {
207
+ const result = { ...s };
208
+ // 迁移内置命令名称
209
+ if (result.tmux) {
210
+ const migrateCommand = (command) => {
211
+ if (command === DEPRECATED_BUILTIN_COMMANDS.AUTO_CLAUDE_OLD) {
212
+ return BUILTIN_COMMANDS.AUTO_CLAUDE;
213
+ }
214
+ if (command === DEPRECATED_BUILTIN_COMMANDS.AUTO_DEV_SERVER_OLD) {
215
+ return BUILTIN_COMMANDS.AUTO_DEV_SERVER;
216
+ }
217
+ return command;
218
+ };
219
+ const migratePaneConfig = (paneConfig) => {
220
+ if (!paneConfig || !paneConfig.command) {
221
+ return paneConfig;
222
+ }
223
+ return {
224
+ ...paneConfig,
225
+ command: migrateCommand(paneConfig.command),
226
+ };
227
+ };
228
+ // 迁移所有 pane 配置
229
+ const migratedTmux = { ...result.tmux };
230
+ migratedTmux.leftPane = migratePaneConfig(migratedTmux.leftPane);
231
+ migratedTmux.rightPane = migratePaneConfig(migratedTmux.rightPane);
232
+ migratedTmux.topPane = migratePaneConfig(migratedTmux.topPane);
233
+ migratedTmux.bottomPane = migratePaneConfig(migratedTmux.bottomPane);
234
+ migratedTmux.topRightPane = migratePaneConfig(migratedTmux.topRightPane);
235
+ migratedTmux.bottomRightPane = migratePaneConfig(migratedTmux.bottomRightPane);
236
+ migratedTmux.topLeftPane = migratePaneConfig(migratedTmux.topLeftPane);
237
+ migratedTmux.bottomLeftPane = migratePaneConfig(migratedTmux.bottomLeftPane);
238
+ result.tmux = migratedTmux;
239
+ }
240
+ // 递归处理 branchOverrides
241
+ if (result.branchOverrides) {
242
+ const migratedOverrides = {};
243
+ for (const [branch, branchSettings] of Object.entries(result.branchOverrides)) {
244
+ migratedOverrides[branch] = migrateSettings(branchSettings);
245
+ }
246
+ result.branchOverrides = migratedOverrides;
247
+ }
248
+ return result;
249
+ };
250
+ return {
251
+ ...migrateSettings(settings),
252
+ version: 3,
253
+ };
254
+ },
255
+ ];
256
+ /**
257
+ * 执行配置迁移
258
+ * @param settings 原始配置
259
+ * @returns 迁移后的配置
260
+ */
261
+ function migrateSettings(settings) {
262
+ const currentVersion = settings.version ?? 0;
263
+ // 如果已经是最新版本,直接返回
264
+ if (currentVersion >= CURRENT_CONFIG_VERSION) {
265
+ return settings;
266
+ }
267
+ // 按顺序执行所有必要的 migration
268
+ let migratedSettings = { ...settings };
269
+ for (let version = currentVersion; version < CURRENT_CONFIG_VERSION; version++) {
270
+ const migrationFn = MIGRATIONS[version];
271
+ if (migrationFn) {
272
+ migratedSettings = migrationFn(migratedSettings);
273
+ }
274
+ else {
275
+ // 如果缺少某个版本的 migration 函数,直接更新版本号
276
+ migratedSettings.version = version + 1;
277
+ }
278
+ }
279
+ return migratedSettings;
280
+ }
281
+ /**
282
+ * 保存配置到文件
283
+ * @param configPath 配置文件路径
284
+ * @param settings 配置对象
285
+ */
286
+ async function saveSettingsToFile(configPath, settings) {
287
+ // 确保配置有版本号
288
+ const settingsWithVersion = {
289
+ ...settings,
290
+ version: settings.version ?? CURRENT_CONFIG_VERSION,
291
+ };
292
+ // 确保目录存在
293
+ const dir = path.dirname(configPath);
294
+ await fs.mkdir(dir, { recursive: true });
295
+ // 写入文件
296
+ await fs.writeFile(configPath, JSON.stringify(settingsWithVersion, null, 2), 'utf-8');
297
+ }
298
+ /**
299
+ * 从设置文件加载完整设置(用于 config 命令显示)
300
+ * 自动执行必要的 migration 并保存更新后的配置
301
+ * @param configPath 设置文件路径
302
+ * @returns 完整设置对象,如果文件不存在或无法解析则返回 null
303
+ */
304
+ export async function loadSettingsFromFile(configPath) {
305
+ try {
306
+ const content = await fs.readFile(configPath, 'utf-8');
307
+ const rawSettings = JSON.parse(content);
308
+ // 执行 migration
309
+ const migratedSettings = migrateSettings(rawSettings);
310
+ // 如果版本号有变化,保存更新后的配置
311
+ const oldVersion = rawSettings.version ?? 0;
312
+ const newVersion = migratedSettings.version ?? CURRENT_CONFIG_VERSION;
313
+ if (oldVersion !== newVersion) {
314
+ await saveSettingsToFile(configPath, migratedSettings);
315
+ }
316
+ return migratedSettings;
317
+ }
318
+ catch {
319
+ return null;
320
+ }
321
+ }
322
+ /**
323
+ * 合并单个 Pane 配置
324
+ */
325
+ function mergePaneConfig(base, override) {
326
+ if (override === undefined) {
327
+ return base;
328
+ }
329
+ if (base === undefined) {
330
+ return override;
331
+ }
332
+ return {
333
+ ...base,
334
+ ...override,
335
+ };
336
+ }
337
+ /**
338
+ * 深度合并两个配置对象
339
+ * 后者的值会覆盖前者,但只覆盖明确设置的字段
340
+ * @param base 基础配置
341
+ * @param override 覆盖配置
342
+ */
343
+ function mergeConfigs(base, override) {
344
+ const result = { ...base };
345
+ // 合并 autoRun(如果 override 中明确设置了)
346
+ if (override.autoRun !== undefined) {
347
+ result.autoRun = override.autoRun;
348
+ }
349
+ // 合并各个 pane 配置
350
+ result.leftPane = mergePaneConfig(base.leftPane, override.leftPane);
351
+ result.topRightPane = mergePaneConfig(base.topRightPane, override.topRightPane);
352
+ result.bottomRightPane = mergePaneConfig(base.bottomRightPane, override.bottomRightPane);
353
+ return result;
354
+ }
355
+ /**
356
+ * 加载 tmux 配置(两层配置机制)
357
+ *
358
+ * 配置加载顺序(优先级从低到高):
359
+ * 1. 内置默认值
360
+ * 2. 用户级配置:~/.config/colyn/settings.json 中的 tmux 字段
361
+ * 3. 项目级配置:{projectRoot}/.colyn/settings.json 中的 tmux 字段
362
+ *
363
+ * @param projectRoot 项目根目录
364
+ * @returns 合并后的配置对象
365
+ */
366
+ export async function loadTmuxConfig(projectRoot) {
367
+ // 并行加载用户级和项目级配置
368
+ const [userConfig, projectConfig] = await Promise.all([
369
+ loadConfigFromFile(getUserConfigPath()),
370
+ loadConfigFromFile(getProjectConfigPath(projectRoot)),
371
+ ]);
372
+ // 从空对象开始,依次合并
373
+ let config = {};
374
+ // 合并用户级配置
375
+ if (userConfig) {
376
+ config = mergeConfigs(config, userConfig);
377
+ }
378
+ // 合并项目级配置(优先级更高)
379
+ if (projectConfig) {
380
+ config = mergeConfigs(config, projectConfig);
381
+ }
382
+ return config;
383
+ }
384
+ /**
385
+ * 检查 worktree 是否存在 Claude session
386
+ * 通过检查 ~/.claude/projects/{encodedPath} 下是否存在会话文件来判断
387
+ * @param worktreePath worktree 路径
388
+ */
389
+ export async function hasClaudeSession(worktreePath) {
390
+ try {
391
+ const projectDir = getClaudeProjectDir(worktreePath);
392
+ const stat = await fs.stat(projectDir);
393
+ if (!stat.isDirectory()) {
394
+ return false;
395
+ }
396
+ const files = await fs.readdir(projectDir);
397
+ return files.some((file) => file.endsWith('.jsonl'));
398
+ }
399
+ catch {
400
+ return false;
401
+ }
402
+ }
403
+ /**
404
+ * 获取 Claude 项目会话目录
405
+ * Claude CLI 将每个目录的会话写入 ~/.claude/projects/{encodedPath}
406
+ */
407
+ function getClaudeProjectDir(worktreePath) {
408
+ const encodedPath = encodeClaudeProjectPath(worktreePath);
409
+ return path.join(os.homedir(), '.claude', 'projects', encodedPath);
410
+ }
411
+ /**
412
+ * Claude 项目目录编码规则:将路径分隔符替换为 '-'
413
+ * 例:/Users/name/project -> -Users-name-project
414
+ */
415
+ function encodeClaudeProjectPath(worktreePath) {
416
+ return worktreePath.split(path.sep).join('-');
417
+ }
418
+ /**
419
+ * 解析 Claude 会话命令
420
+ * - 如果存在 .claude 目录,运行 `claude -c` 继续会话
421
+ * - 否则运行 `claude` 启动新会话
422
+ * @param worktreePath worktree 路径
423
+ * @param claudeCommand 自定义 Claude 命令(默认为 'claude')
424
+ */
425
+ async function resolveClaudeCommand(worktreePath, claudeCommand = 'claude') {
426
+ const hasSession = await hasClaudeSession(worktreePath);
427
+ return hasSession ? `${claudeCommand} -c` : claudeCommand;
428
+ }
429
+ /**
430
+ * 解析 dev server 命令
431
+ * 使用 dev-server 模块检测 dev 命令
432
+ * @param worktreePath worktree 路径
433
+ */
434
+ async function resolveDevServerCommand(worktreePath) {
435
+ return await getDevServerCommand(worktreePath);
436
+ }
437
+ /**
438
+ * 解析单个 pane 命令
439
+ * @param command 命令配置值
440
+ * @param worktreePath worktree 路径
441
+ * @param claudeCommand 自定义 Claude 命令
442
+ */
443
+ async function resolvePaneCommand(command, worktreePath, claudeCommand) {
444
+ // 如果是 null 或 undefined,不执行命令
445
+ if (command === null || command === undefined) {
446
+ return undefined;
447
+ }
448
+ // 处理内置命令
449
+ switch (command) {
450
+ case BUILTIN_COMMANDS.AUTO_CLAUDE:
451
+ return await resolveClaudeCommand(worktreePath, claudeCommand);
452
+ case BUILTIN_COMMANDS.AUTO_DEV_SERVER:
453
+ return await resolveDevServerCommand(worktreePath);
454
+ default:
455
+ // 自定义命令,直接返回
456
+ return command;
457
+ }
458
+ }
459
+ /**
460
+ * 解析百分比字符串为数字
461
+ * @param sizeStr 大小字符串(如 "50%")
462
+ * @param defaultValue 默认值
463
+ */
464
+ function parsePercentage(sizeStr, defaultValue) {
465
+ if (!sizeStr) {
466
+ return defaultValue;
467
+ }
468
+ const match = sizeStr.match(/^(\d+)%?$/);
469
+ if (match) {
470
+ return parseInt(match[1], 10);
471
+ }
472
+ return defaultValue;
473
+ }
474
+ /**
475
+ * 解析所有 pane 命令
476
+ * @param config tmux 配置
477
+ * @param worktreePath worktree 路径
478
+ * @param projectRoot 项目根目录(可选,用于读取 claudeCommand 配置)
479
+ * @param branchName 分支名称(可选,用于读取分支特定的 claudeCommand 配置)
480
+ */
481
+ export async function resolvePaneCommands(config, worktreePath, projectRoot, branchName) {
482
+ // 如果禁用自动运行,返回空对象
483
+ const autoRun = config.autoRun !== undefined ? config.autoRun : DEFAULT_CONFIG.autoRun;
484
+ if (!autoRun) {
485
+ return {};
486
+ }
487
+ // 读取 claude 命令配置
488
+ let claudeCommand = 'claude';
489
+ if (projectRoot && branchName) {
490
+ const settings = await loadSettingsForBranch(projectRoot, branchName);
491
+ claudeCommand = settings.systemCommands?.claude ?? 'claude';
492
+ }
493
+ // 检测布局类型
494
+ const layout = detectLayoutType(config);
495
+ // 根据布局类型解析命令
496
+ switch (layout) {
497
+ case 'single-pane':
498
+ // 单窗格:无命令
499
+ return {};
500
+ case 'two-pane-horizontal':
501
+ return await resolveTwoPaneHorizontalCommands(config, worktreePath, claudeCommand);
502
+ case 'two-pane-vertical':
503
+ return await resolveTwoPaneVerticalCommands(config, worktreePath, claudeCommand);
504
+ case 'three-pane':
505
+ return await resolveThreePaneCommands(config, worktreePath, claudeCommand);
506
+ case 'four-pane':
507
+ return await resolveFourPaneCommands(config, worktreePath, claudeCommand);
508
+ default:
509
+ // 默认使用三窗格
510
+ return await resolveThreePaneCommands(config, worktreePath, claudeCommand);
511
+ }
512
+ }
513
+ /**
514
+ * 解析两窗格水平布局的命令
515
+ */
516
+ async function resolveTwoPaneHorizontalCommands(config, worktreePath, claudeCommand) {
517
+ const leftCommand = config.leftPane?.command;
518
+ const rightCommand = config.rightPane?.command;
519
+ const [pane0, pane1] = await Promise.all([
520
+ resolvePaneCommand(leftCommand, worktreePath, claudeCommand),
521
+ resolvePaneCommand(rightCommand, worktreePath, claudeCommand),
522
+ ]);
523
+ return { pane0, pane1 };
524
+ }
525
+ /**
526
+ * 解析两窗格垂直布局的命令
527
+ */
528
+ async function resolveTwoPaneVerticalCommands(config, worktreePath, claudeCommand) {
529
+ const topCommand = config.topPane?.command;
530
+ const bottomCommand = config.bottomPane?.command;
531
+ const [pane0, pane1] = await Promise.all([
532
+ resolvePaneCommand(topCommand, worktreePath, claudeCommand),
533
+ resolvePaneCommand(bottomCommand, worktreePath, claudeCommand),
534
+ ]);
535
+ return { pane0, pane1 };
536
+ }
537
+ /**
538
+ * 解析三窗格布局的命令
539
+ */
540
+ async function resolveThreePaneCommands(config, worktreePath, claudeCommand) {
541
+ // 使用默认值(向后兼容)
542
+ const leftCommand = config.leftPane?.command !== undefined
543
+ ? config.leftPane.command
544
+ : DEFAULT_CONFIG.leftPane.command;
545
+ const topRightCommand = config.topRightPane?.command !== undefined
546
+ ? config.topRightPane.command
547
+ : DEFAULT_CONFIG.topRightPane.command;
548
+ const bottomRightCommand = config.bottomRightPane?.command !== undefined
549
+ ? config.bottomRightPane.command
550
+ : DEFAULT_CONFIG.bottomRightPane.command;
551
+ const [pane0, pane1, pane2] = await Promise.all([
552
+ resolvePaneCommand(leftCommand, worktreePath, claudeCommand),
553
+ resolvePaneCommand(topRightCommand, worktreePath, claudeCommand),
554
+ resolvePaneCommand(bottomRightCommand, worktreePath, claudeCommand),
555
+ ]);
556
+ return { pane0, pane1, pane2 };
557
+ }
558
+ /**
559
+ * 解析四窗格布局的命令
560
+ */
561
+ async function resolveFourPaneCommands(config, worktreePath, claudeCommand) {
562
+ const topLeftCommand = config.topLeftPane?.command;
563
+ const topRightCommand = config.topRightPane?.command;
564
+ const bottomLeftCommand = config.bottomLeftPane?.command;
565
+ const bottomRightCommand = config.bottomRightPane?.command;
566
+ const [pane0, pane1, pane2, pane3] = await Promise.all([
567
+ resolvePaneCommand(topLeftCommand, worktreePath, claudeCommand),
568
+ resolvePaneCommand(topRightCommand, worktreePath, claudeCommand),
569
+ resolvePaneCommand(bottomLeftCommand, worktreePath, claudeCommand),
570
+ resolvePaneCommand(bottomRightCommand, worktreePath, claudeCommand),
571
+ ]);
572
+ return { pane0, pane1, pane2, pane3 };
573
+ }
574
+ /**
575
+ * 解析 Pane 布局配置
576
+ * @param config tmux 配置
577
+ */
578
+ export function resolvePaneLayout(config) {
579
+ const layout = detectLayoutType(config);
580
+ const result = { layout };
581
+ switch (layout) {
582
+ case 'single-pane':
583
+ // 单窗格,无需配置
584
+ break;
585
+ case 'two-pane-horizontal':
586
+ // 水平两分割:leftPane size
587
+ result.leftSize = parsePercentage(config.leftPane?.size, 50);
588
+ break;
589
+ case 'two-pane-vertical':
590
+ // 垂直两分割:topPane size
591
+ result.topSize = parsePercentage(config.topPane?.size, 50);
592
+ break;
593
+ case 'three-pane':
594
+ // 三窗格:leftPane size 和 topRightPane size
595
+ result.leftSize = parsePercentage(config.leftPane?.size, parsePercentage(DEFAULT_CONFIG.leftPane.size, 60));
596
+ result.rightTopSize = parsePercentage(config.topRightPane?.size, parsePercentage(DEFAULT_CONFIG.topRightPane.size, 30));
597
+ break;
598
+ case 'four-pane':
599
+ // 四窗格:根据配置的 split 类型决定
600
+ if (config.horizontalSplit) {
601
+ result.horizontalSplit = parsePercentage(config.horizontalSplit, 50);
602
+ }
603
+ if (config.verticalSplit) {
604
+ result.verticalSplit = parsePercentage(config.verticalSplit, 50);
605
+ }
606
+ // 如果两个 split 都配置了,忽略 pane size
607
+ if (result.horizontalSplit !== undefined && result.verticalSplit !== undefined) {
608
+ // 同时配置:忽略 pane size
609
+ break;
610
+ }
611
+ // 如果只配置了一个 split,解析 pane size
612
+ result.topLeftSize = parsePercentage(config.topLeftPane?.size, 50);
613
+ result.topRightSize = parsePercentage(config.topRightPane?.size, 50);
614
+ result.bottomLeftSize = parsePercentage(config.bottomLeftPane?.size, 50);
615
+ result.bottomRightSize = parsePercentage(config.bottomRightPane?.size, 50);
616
+ break;
617
+ }
618
+ return result;
619
+ }
620
+ /**
621
+ * 检测布局类型
622
+ * 根据配置的窗格自动检测布局类型(向后兼容)
623
+ * @param config tmux 配置
624
+ * @returns 检测到的布局类型
625
+ */
626
+ export function detectLayoutType(config) {
627
+ // 如果明确指定了 layout,直接返回
628
+ if (config.layout) {
629
+ return config.layout;
630
+ }
631
+ // 检查是否配置了任何窗格
632
+ const hasLeftPane = config.leftPane !== undefined;
633
+ const hasTopRightPane = config.topRightPane !== undefined;
634
+ const hasBottomRightPane = config.bottomRightPane !== undefined;
635
+ const hasRightPane = config.rightPane !== undefined;
636
+ const hasTopPane = config.topPane !== undefined;
637
+ const hasBottomPane = config.bottomPane !== undefined;
638
+ const hasTopLeftPane = config.topLeftPane !== undefined;
639
+ const hasBottomLeftPane = config.bottomLeftPane !== undefined;
640
+ // 四窗格:配置了任何一个四窗格特有的窗格
641
+ if (hasTopLeftPane || hasBottomLeftPane) {
642
+ return 'four-pane';
643
+ }
644
+ // 两窗格水平:配置了 rightPane
645
+ if (hasRightPane) {
646
+ return 'two-pane-horizontal';
647
+ }
648
+ // 两窗格垂直:配置了 topPane 或 bottomPane
649
+ if (hasTopPane || hasBottomPane) {
650
+ return 'two-pane-vertical';
651
+ }
652
+ // 三窗格:配置了 leftPane, topRightPane 或 bottomRightPane
653
+ if (hasLeftPane || hasTopRightPane || hasBottomRightPane) {
654
+ return 'three-pane';
655
+ }
656
+ // 默认:三窗格布局
657
+ return 'three-pane';
658
+ }
659
+ /**
660
+ * 验证 tmux 配置
661
+ * @param config tmux 配置
662
+ * @returns 验证结果
663
+ */
664
+ export function validateTmuxConfig(config) {
665
+ const errors = [];
666
+ const warnings = [];
667
+ // 检测布局类型
668
+ const layout = detectLayoutType(config);
669
+ const supportedPanes = LAYOUT_PANES[layout];
670
+ // 检查所有可能的窗格配置
671
+ const allPaneNames = [
672
+ 'leftPane',
673
+ 'rightPane',
674
+ 'topPane',
675
+ 'bottomPane',
676
+ 'topRightPane',
677
+ 'bottomRightPane',
678
+ 'topLeftPane',
679
+ 'bottomLeftPane',
680
+ ];
681
+ for (const paneName of allPaneNames) {
682
+ const paneConfig = config[paneName];
683
+ if (paneConfig === undefined) {
684
+ continue;
685
+ }
686
+ // 检查当前布局是否支持此窗格
687
+ if (!supportedPanes.includes(paneName)) {
688
+ warnings.push(`Layout "${layout}" does not support pane "${paneName}", it will be ignored. ` +
689
+ `Supported panes: ${supportedPanes.join(', ')}`);
690
+ }
691
+ // 验证 size 配置
692
+ if (paneConfig.size) {
693
+ const size = parsePercentage(paneConfig.size, -1);
694
+ if (size < 0 || size > 100) {
695
+ warnings.push(`Pane "${paneName}" size "${paneConfig.size}" is out of reasonable range (0%-100%), ` +
696
+ `using default value`);
697
+ }
698
+ }
699
+ }
700
+ return {
701
+ hasError: errors.length > 0,
702
+ errors,
703
+ warnings,
704
+ };
705
+ }
706
+ /**
707
+ * 匹配通配符模式
708
+ * @param pattern 通配符模式(如 "feature/*")
709
+ * @param branchName 分支名称
710
+ * @returns 是否匹配
711
+ */
712
+ function matchWildcard(pattern, branchName) {
713
+ // 将通配符模式转换为正则表达式
714
+ // 支持 * 匹配任意字符
715
+ const regexPattern = pattern
716
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
717
+ .replace(/\*/g, '.*'); // * 替换为 .*
718
+ const regex = new RegExp(`^${regexPattern}$`);
719
+ return regex.test(branchName);
720
+ }
721
+ /**
722
+ * 从设置中加载特定分支的配置
723
+ * @param settings 完整设置对象
724
+ * @param branchName 分支名称
725
+ * @returns 合并后的设置
726
+ */
727
+ export function loadBranchSettings(settings, branchName) {
728
+ const branchOverrides = settings.branchOverrides;
729
+ if (!branchOverrides) {
730
+ return settings;
731
+ }
732
+ // 优先级:精确匹配 > 通配符匹配
733
+ const overrideKeys = Object.keys(branchOverrides);
734
+ // 1. 精确匹配
735
+ if (overrideKeys.includes(branchName)) {
736
+ const branchConfig = branchOverrides[branchName];
737
+ return mergeSettings(settings, branchConfig);
738
+ }
739
+ // 2. 通配符匹配(找到第一个匹配的)
740
+ for (const pattern of overrideKeys) {
741
+ if (matchWildcard(pattern, branchName)) {
742
+ const branchConfig = branchOverrides[pattern];
743
+ return mergeSettings(settings, branchConfig);
744
+ }
745
+ }
746
+ // 3. 无匹配,返回基础配置
747
+ return settings;
748
+ }
749
+ /**
750
+ * 深度合并两个 Settings 对象
751
+ * @param base 基础设置
752
+ * @param override 覆盖设置
753
+ * @returns 合并后的设置
754
+ */
755
+ function mergeSettings(base, override) {
756
+ const result = { ...base };
757
+ // 合并顶层字段
758
+ if (override.lang !== undefined) {
759
+ result.lang = override.lang;
760
+ }
761
+ // 深度合并 systemCommands 配置
762
+ if (override.systemCommands !== undefined) {
763
+ result.systemCommands = {
764
+ ...base.systemCommands,
765
+ ...override.systemCommands,
766
+ };
767
+ }
768
+ // 深度合并 tmux 配置
769
+ if (override.tmux !== undefined) {
770
+ result.tmux = base.tmux
771
+ ? mergeConfigs(base.tmux, override.tmux)
772
+ : override.tmux;
773
+ }
774
+ // 注意:branchOverrides 不需要合并(只在顶层使用)
775
+ return result;
776
+ }
777
+ /**
778
+ * 从 branchOverrides 中查找匹配的分支配置
779
+ * @param branchOverrides 分支覆盖配置
780
+ * @param branchName 分支名称
781
+ * @returns 匹配的分支配置,如果没有匹配则返回 null
782
+ */
783
+ function findBranchOverride(branchOverrides, branchName) {
784
+ const overrideKeys = Object.keys(branchOverrides);
785
+ // 1. 精确匹配
786
+ if (overrideKeys.includes(branchName)) {
787
+ return branchOverrides[branchName];
788
+ }
789
+ // 2. 通配符匹配(找到第一个匹配的)
790
+ for (const pattern of overrideKeys) {
791
+ if (matchWildcard(pattern, branchName)) {
792
+ return branchOverrides[pattern];
793
+ }
794
+ }
795
+ // 3. 无匹配
796
+ return null;
797
+ }
798
+ /**
799
+ * 加载特定分支的完整设置
800
+ *
801
+ * 配置优先级(从低到高):
802
+ * 1. User default(用户级全局配置)
803
+ * 2. Project default(项目级全局配置)
804
+ * 3. User override(用户级分支覆盖)
805
+ * 4. Project override(项目级分支覆盖)
806
+ *
807
+ * @param projectRoot 项目根目录
808
+ * @param branchName 分支名称
809
+ * @returns 合并后的完整设置
810
+ */
811
+ export async function loadSettingsForBranch(projectRoot, branchName) {
812
+ // 并行加载用户级和项目级配置
813
+ const [userSettings, projectSettings] = await Promise.all([
814
+ loadSettingsFromFile(getUserConfigPath()),
815
+ loadSettingsFromFile(getProjectConfigPath(projectRoot)),
816
+ ]);
817
+ // 从空设置开始
818
+ let settings = {};
819
+ // 1. User default - 用户级全局配置
820
+ if (userSettings) {
821
+ settings = mergeSettings(settings, userSettings);
822
+ }
823
+ // 2. Project default - 项目级全局配置
824
+ if (projectSettings) {
825
+ settings = mergeSettings(settings, projectSettings);
826
+ }
827
+ // 3. User override - 用户级分支覆盖
828
+ if (userSettings?.branchOverrides) {
829
+ const userBranchConfig = findBranchOverride(userSettings.branchOverrides, branchName);
830
+ if (userBranchConfig) {
831
+ settings = mergeSettings(settings, userBranchConfig);
832
+ }
833
+ }
834
+ // 4. Project override - 项目级分支覆盖
835
+ if (projectSettings?.branchOverrides) {
836
+ const projectBranchConfig = findBranchOverride(projectSettings.branchOverrides, branchName);
837
+ if (projectBranchConfig) {
838
+ settings = mergeSettings(settings, projectBranchConfig);
839
+ }
840
+ }
841
+ return settings;
842
+ }
843
+ /**
844
+ * 加载特定分支的 tmux 配置
845
+ *
846
+ * 配置优先级(从低到高):
847
+ * 1. System builtin(系统内置默认,如 main 分支默认单窗格)
848
+ * 2. User default(用户级全局配置,~/.config/colyn/settings.json 的 tmux)
849
+ * 3. Project default(项目级全局配置,.colyn/settings.json 的 tmux)
850
+ * 4. User override(用户级分支覆盖,~/.config/colyn/settings.json 的 branchOverrides[branch].tmux)
851
+ * 5. Project override(项目级分支覆盖,.colyn/settings.json 的 branchOverrides[branch].tmux)
852
+ *
853
+ * 全部按照字段级覆盖(field-level override)
854
+ *
855
+ * @param projectRoot 项目根目录
856
+ * @param branchName 分支名称
857
+ * @returns 合并后的 tmux 配置
858
+ */
859
+ export async function loadTmuxConfigForBranch(projectRoot, branchName) {
860
+ // 并行加载用户级和项目级配置
861
+ const [userSettings, projectSettings] = await Promise.all([
862
+ loadSettingsFromFile(getUserConfigPath()),
863
+ loadSettingsFromFile(getProjectConfigPath(projectRoot)),
864
+ ]);
865
+ // 从空配置开始
866
+ let config = {};
867
+ // 1. System builtin - 系统内置分支默认配置
868
+ const builtinBranchDefault = BUILTIN_BRANCH_DEFAULTS[branchName];
869
+ if (builtinBranchDefault) {
870
+ config = mergeConfigs(config, builtinBranchDefault);
871
+ }
872
+ // 2. User default - 用户级全局配置
873
+ if (userSettings?.tmux) {
874
+ config = mergeConfigs(config, userSettings.tmux);
875
+ }
876
+ // 3. Project default - 项目级全局配置
877
+ if (projectSettings?.tmux) {
878
+ config = mergeConfigs(config, projectSettings.tmux);
879
+ }
880
+ // 4. User override - 用户级分支覆盖
881
+ if (userSettings?.branchOverrides) {
882
+ const userBranchConfig = findBranchOverride(userSettings.branchOverrides, branchName);
883
+ if (userBranchConfig?.tmux) {
884
+ config = mergeConfigs(config, userBranchConfig.tmux);
885
+ }
886
+ }
887
+ // 5. Project override - 项目级分支覆盖
888
+ if (projectSettings?.branchOverrides) {
889
+ const projectBranchConfig = findBranchOverride(projectSettings.branchOverrides, branchName);
890
+ if (projectBranchConfig?.tmux) {
891
+ config = mergeConfigs(config, projectBranchConfig.tmux);
892
+ }
893
+ }
894
+ return config;
895
+ }
896
+ //# sourceMappingURL=tmux-config.legacy.js.map