yg-team-cli 2.0.0 → 2.1.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.
package/dist/index.js CHANGED
@@ -1,173 +1,864 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
2
11
 
3
- // src/index.ts
4
- import { Command as Command13 } from "commander";
5
- import chalk2 from "chalk";
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
6
20
 
7
21
  // src/lib/logger.ts
8
22
  import chalk from "chalk";
9
23
  import ora from "ora";
10
- var Logger = class {
11
- spinner = null;
12
- /**
13
- * 打印标题(加粗)
14
- */
15
- header(text) {
16
- console.log(chalk.bold(text));
17
- }
18
- /**
19
- * 打印成功信息(绿色)
20
- */
21
- success(text) {
22
- console.log(chalk.green("\u2713"), text);
23
- }
24
- /**
25
- * 打印错误信息(红色)
26
- */
27
- error(text) {
28
- console.error(chalk.red("\u2717"), text);
29
- }
30
- /**
31
- * 打印警告信息(黄色)
32
- */
33
- warn(text) {
34
- console.warn(chalk.yellow("\u26A0"), text);
35
- }
36
- /**
37
- * 打印信息(蓝色)
38
- */
39
- info(text) {
40
- console.info(chalk.blue("\u2139"), text);
41
- }
42
- /**
43
- * 打印步骤信息
44
- */
45
- step(text) {
46
- console.log(chalk.cyan("\u2192"), text);
47
- }
48
- /**
49
- * 打印空行
50
- */
51
- newLine() {
52
- console.log("");
53
- }
54
- /**
55
- * 打印分隔线
56
- */
57
- separator(char = "\u2500", length = 50) {
58
- console.log(chalk.gray(char.repeat(length)));
59
- }
60
- /**
61
- * 开始加载动画
62
- */
63
- startLoading(text) {
64
- this.spinner = ora({
65
- text,
66
- color: "cyan"
67
- }).start();
68
- return this.spinner;
69
- }
70
- /**
71
- * 更新加载文本
72
- */
73
- updateLoading(text) {
74
- if (this.spinner) {
75
- this.spinner.text = text;
76
- }
77
- }
78
- /**
79
- * 停止加载并显示成功
80
- */
81
- succeedLoading(text) {
82
- if (this.spinner) {
83
- this.spinner.succeed(text);
84
- this.spinner = null;
85
- }
86
- }
87
- /**
88
- * 停止加载并显示失败
89
- */
90
- failLoading(text) {
91
- if (this.spinner) {
92
- this.spinner.fail(text);
93
- this.spinner = null;
94
- }
95
- }
96
- /**
97
- * 停止加载并显示警告
98
- */
99
- warnLoading(text) {
100
- if (this.spinner) {
101
- this.spinner.warn(text);
102
- this.spinner = null;
103
- }
104
- }
105
- /**
106
- * 停止加载并显示信息
107
- */
108
- infoLoading(text) {
109
- if (this.spinner) {
110
- this.spinner.info(text);
111
- this.spinner = null;
112
- }
113
- }
114
- /**
115
- * 打印表格
116
- */
117
- table(headers, rows) {
118
- const columnWidths = headers.map((h, i) => {
119
- const maxWidth = Math.max(
120
- h.length,
121
- ...rows.map((row) => (row[i] || "").length)
122
- );
123
- return maxWidth + 2;
124
- });
125
- const headerRow = headers.map((h, i) => chalk.bold(h.padEnd(columnWidths[i]))).join("");
126
- console.log(headerRow);
127
- const separator = columnWidths.map((w) => "\u2500".repeat(w)).join("\u253C");
128
- console.log(chalk.gray(separator));
129
- rows.forEach((row) => {
130
- const dataRow = row.map((cell, i) => (cell || "").padEnd(columnWidths[i])).join("");
131
- console.log(dataRow);
132
- });
24
+ var Logger, logger;
25
+ var init_logger = __esm({
26
+ "src/lib/logger.ts"() {
27
+ "use strict";
28
+ init_esm_shims();
29
+ Logger = class {
30
+ spinner = null;
31
+ /**
32
+ * 打印标题(加粗)
33
+ */
34
+ header(text) {
35
+ console.log(chalk.bold(text));
36
+ }
37
+ /**
38
+ * 打印成功信息(绿色)
39
+ */
40
+ success(text) {
41
+ console.log(chalk.green("\u2713"), text);
42
+ }
43
+ /**
44
+ * 打印错误信息(红色)
45
+ */
46
+ error(text) {
47
+ console.error(chalk.red("\u2717"), text);
48
+ }
49
+ /**
50
+ * 打印警告信息(黄色)
51
+ */
52
+ warn(text) {
53
+ console.warn(chalk.yellow("\u26A0"), text);
54
+ }
55
+ /**
56
+ * 打印信息(蓝色)
57
+ */
58
+ info(text) {
59
+ console.info(chalk.blue("\u2139"), text);
60
+ }
61
+ /**
62
+ * 打印步骤信息
63
+ */
64
+ step(text) {
65
+ console.log(chalk.cyan("\u2192"), text);
66
+ }
67
+ /**
68
+ * 打印空行
69
+ */
70
+ newLine() {
71
+ console.log("");
72
+ }
73
+ /**
74
+ * 打印分隔线
75
+ */
76
+ separator(char = "\u2500", length = 50) {
77
+ console.log(chalk.gray(char.repeat(length)));
78
+ }
79
+ /**
80
+ * 开始加载动画
81
+ */
82
+ startLoading(text) {
83
+ this.spinner = ora({
84
+ text,
85
+ color: "cyan"
86
+ }).start();
87
+ return this.spinner;
88
+ }
89
+ /**
90
+ * 更新加载文本
91
+ */
92
+ updateLoading(text) {
93
+ if (this.spinner) {
94
+ this.spinner.text = text;
95
+ }
96
+ }
97
+ /**
98
+ * 停止加载并显示成功
99
+ */
100
+ succeedLoading(text) {
101
+ if (this.spinner) {
102
+ this.spinner.succeed(text);
103
+ this.spinner = null;
104
+ }
105
+ }
106
+ /**
107
+ * 停止加载并显示失败
108
+ */
109
+ failLoading(text) {
110
+ if (this.spinner) {
111
+ this.spinner.fail(text);
112
+ this.spinner = null;
113
+ }
114
+ }
115
+ /**
116
+ * 停止加载并显示警告
117
+ */
118
+ warnLoading(text) {
119
+ if (this.spinner) {
120
+ this.spinner.warn(text);
121
+ this.spinner = null;
122
+ }
123
+ }
124
+ /**
125
+ * 停止加载并显示信息
126
+ */
127
+ infoLoading(text) {
128
+ if (this.spinner) {
129
+ this.spinner.info(text);
130
+ this.spinner = null;
131
+ }
132
+ }
133
+ /**
134
+ * 打印表格
135
+ */
136
+ table(headers, rows) {
137
+ const columnWidths = headers.map((h, i) => {
138
+ const maxWidth = Math.max(
139
+ h.length,
140
+ ...rows.map((row) => (row[i] || "").length)
141
+ );
142
+ return maxWidth + 2;
143
+ });
144
+ const headerRow = headers.map((h, i) => chalk.bold(h.padEnd(columnWidths[i]))).join("");
145
+ console.log(headerRow);
146
+ const separator = columnWidths.map((w) => "\u2500".repeat(w)).join("\u253C");
147
+ console.log(chalk.gray(separator));
148
+ rows.forEach((row) => {
149
+ const dataRow = row.map((cell, i) => (cell || "").padEnd(columnWidths[i])).join("");
150
+ console.log(dataRow);
151
+ });
152
+ }
153
+ /**
154
+ * 打印代码块
155
+ */
156
+ code(code, language = "") {
157
+ if (language) {
158
+ console.log(chalk.gray(`\`\`\`${language}`));
159
+ }
160
+ console.log(chalk.gray(code));
161
+ if (language) {
162
+ console.log(chalk.gray("```"));
163
+ }
164
+ }
165
+ /**
166
+ * 打印列表
167
+ */
168
+ list(items, indent = 2) {
169
+ items.forEach((item) => {
170
+ console.log(" ".repeat(indent) + chalk.gray("\u2022") + " " + item);
171
+ });
172
+ }
173
+ /**
174
+ * 打印带标签的文本
175
+ */
176
+ labeled(label, text, labelColor = "blue") {
177
+ const coloredLabel = chalk[labelColor](`[${label}]`);
178
+ console.log(`${coloredLabel} ${text}`);
179
+ }
180
+ };
181
+ logger = new Logger();
133
182
  }
134
- /**
135
- * 打印代码块
136
- */
137
- code(code, language = "") {
138
- if (language) {
139
- console.log(chalk.gray(`\`\`\`${language}`));
140
- }
141
- console.log(chalk.gray(code));
142
- if (language) {
143
- console.log(chalk.gray("```"));
144
- }
183
+ });
184
+
185
+ // src/lib/utils.ts
186
+ import fs from "fs-extra";
187
+ import path2 from "path";
188
+ import { glob } from "glob";
189
+ var FileUtils, StringUtils2, DateUtils, GitUtils, SpecUtils;
190
+ var init_utils = __esm({
191
+ "src/lib/utils.ts"() {
192
+ "use strict";
193
+ init_esm_shims();
194
+ FileUtils = class {
195
+ /**
196
+ * 确保目录存在
197
+ */
198
+ static async ensureDir(dir) {
199
+ await fs.ensureDir(dir);
200
+ }
201
+ /**
202
+ * 读取文件内容
203
+ */
204
+ static async read(file) {
205
+ return await fs.readFile(file, "utf-8");
206
+ }
207
+ /**
208
+ * 写入文件内容
209
+ */
210
+ static async write(file, content) {
211
+ await fs.writeFile(file, content, "utf-8");
212
+ }
213
+ /**
214
+ * 检查文件是否存在
215
+ */
216
+ static async exists(file) {
217
+ return await fs.pathExists(file);
218
+ }
219
+ /**
220
+ * 复制文件
221
+ */
222
+ static async copy(src, dest) {
223
+ await fs.copy(src, dest);
224
+ }
225
+ /**
226
+ * 删除文件或目录
227
+ */
228
+ static async remove(file) {
229
+ await fs.remove(file);
230
+ }
231
+ /**
232
+ * 移动文件
233
+ */
234
+ static async move(src, dest) {
235
+ await fs.move(src, dest);
236
+ }
237
+ /**
238
+ * 使用 glob 查找文件
239
+ */
240
+ static async findFiles(pattern, cwd) {
241
+ return await glob(pattern, {
242
+ cwd,
243
+ absolute: false,
244
+ nodir: true
245
+ });
246
+ }
247
+ /**
248
+ * 读取 JSON 文件
249
+ */
250
+ static async readJson(file) {
251
+ return await fs.readJson(file);
252
+ }
253
+ /**
254
+ * 写入 JSON 文件
255
+ */
256
+ static async writeJson(file, data) {
257
+ await fs.writeJson(file, data, { spaces: 2 });
258
+ }
259
+ };
260
+ StringUtils2 = class {
261
+ /**
262
+ * 转换为 kebab-case
263
+ */
264
+ static toKebabCase(str) {
265
+ return str.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
266
+ }
267
+ /**
268
+ * 转换为 PascalCase
269
+ */
270
+ static toPascalCase(str) {
271
+ return str.replace(/[-_\s](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
272
+ }
273
+ /**
274
+ * 转换为 camelCase
275
+ */
276
+ static toCamelCase(str) {
277
+ const pascal = this.toPascalCase(str);
278
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
279
+ }
280
+ /**
281
+ * 转换为 snake_case
282
+ */
283
+ static toSnakeCase(str) {
284
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/^_/, "").replace(/-+/g, "_").replace(/[\s]+/g, "_");
285
+ }
286
+ /**
287
+ * 首字母大写
288
+ */
289
+ static capitalize(str) {
290
+ return str.charAt(0).toUpperCase() + str.slice(1);
291
+ }
292
+ /**
293
+ * 截断字符串
294
+ */
295
+ static truncate(str, length, suffix = "...") {
296
+ if (str.length <= length) return str;
297
+ return str.slice(0, length - suffix.length) + suffix;
298
+ }
299
+ /**
300
+ * 生成 slug
301
+ */
302
+ static slugify(str) {
303
+ return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
304
+ }
305
+ };
306
+ DateUtils = class _DateUtils {
307
+ /**
308
+ * 格式化日期
309
+ */
310
+ static format(date = /* @__PURE__ */ new Date(), format = "YYYY-MM-DD HH:mm:ss") {
311
+ const year = date.getFullYear();
312
+ const month = String(date.getMonth() + 1).padStart(2, "0");
313
+ const day = String(date.getDate()).padStart(2, "0");
314
+ const hours = String(date.getHours()).padStart(2, "0");
315
+ const minutes = String(date.getMinutes()).padStart(2, "0");
316
+ const seconds = String(date.getSeconds()).padStart(2, "0");
317
+ return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
318
+ }
319
+ /**
320
+ * 获取相对时间
321
+ */
322
+ static relative(date) {
323
+ const now = /* @__PURE__ */ new Date();
324
+ const diff = now.getTime() - date.getTime();
325
+ const seconds = Math.floor(diff / 1e3);
326
+ const minutes = Math.floor(seconds / 60);
327
+ const hours = Math.floor(minutes / 60);
328
+ const days = Math.floor(hours / 24);
329
+ if (days > 7) {
330
+ return _DateUtils.format(date, "YYYY-MM-DD");
331
+ } else if (days > 0) {
332
+ return `${days} \u5929\u524D`;
333
+ } else if (hours > 0) {
334
+ return `${hours} \u5C0F\u65F6\u524D`;
335
+ } else if (minutes > 0) {
336
+ return `${minutes} \u5206\u949F\u524D`;
337
+ } else {
338
+ return "\u521A\u521A";
339
+ }
340
+ }
341
+ };
342
+ GitUtils = class {
343
+ /**
344
+ * 检查是否在 Git 仓库中
345
+ */
346
+ static async isGitRepo(cwd = process.cwd()) {
347
+ const gitDir = path2.join(cwd, ".git");
348
+ return await FileUtils.exists(gitDir);
349
+ }
350
+ /**
351
+ * 获取当前分支名
352
+ */
353
+ static async getCurrentBranch(cwd = process.cwd()) {
354
+ const { execa: execa5 } = await import("execa");
355
+ const { stdout } = await execa5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
356
+ cwd
357
+ });
358
+ return stdout.trim();
359
+ }
360
+ /**
361
+ * 获取当前 commit hash
362
+ */
363
+ static async getCurrentCommit(cwd = process.cwd()) {
364
+ const { execa: execa5 } = await import("execa");
365
+ const { stdout } = await execa5("git", ["rev-parse", "HEAD"], { cwd });
366
+ return stdout.trim().slice(0, 7);
367
+ }
368
+ };
369
+ SpecUtils = class {
370
+ /**
371
+ * 解析 spec 文件
372
+ */
373
+ static async parseSpec(file) {
374
+ const content = await FileUtils.read(file);
375
+ const spec = {};
376
+ const titleMatch = content.match(/^#\s+(.+)$/m);
377
+ if (titleMatch) {
378
+ spec.title = titleMatch[1];
379
+ }
380
+ const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
381
+ if (nameMatch) {
382
+ spec.name = nameMatch[1];
383
+ }
384
+ const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
385
+ if (priorityMatch) {
386
+ spec.priority = priorityMatch[1];
387
+ }
388
+ const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
389
+ if (statusMatch) {
390
+ spec.status = statusMatch[1];
391
+ }
392
+ const depSection = content.match(/## 依赖关系\s+([\s\S]+?)##/m);
393
+ if (depSection) {
394
+ const depMatches = depSection[1].matchAll(/- \[([ x])\]\s*(.+)$/gm);
395
+ spec.dependencies = Array.from(depMatches).map((m) => ({
396
+ completed: m[1] === "x",
397
+ name: m[2]
398
+ }));
399
+ }
400
+ return spec;
401
+ }
402
+ /**
403
+ * 获取 spec 状态
404
+ */
405
+ static async getSpecStatus(file) {
406
+ try {
407
+ const spec = await this.parseSpec(file);
408
+ return spec.status || "\u672A\u77E5";
409
+ } catch {
410
+ return "\u9519\u8BEF";
411
+ }
412
+ }
413
+ };
145
414
  }
146
- /**
147
- * 打印列表
148
- */
149
- list(items, indent = 2) {
150
- items.forEach((item) => {
151
- console.log(" ".repeat(indent) + chalk.gray("\u2022") + " " + item);
152
- });
415
+ });
416
+
417
+ // src/lib/user-config.ts
418
+ var user_config_exports = {};
419
+ __export(user_config_exports, {
420
+ UserConfigManager: () => UserConfigManager,
421
+ userConfigManager: () => userConfigManager
422
+ });
423
+ import path4 from "path";
424
+ import os from "os";
425
+ import crypto from "crypto";
426
+ var UserConfigManager, userConfigManager;
427
+ var init_user_config = __esm({
428
+ "src/lib/user-config.ts"() {
429
+ "use strict";
430
+ init_esm_shims();
431
+ init_utils();
432
+ init_logger();
433
+ UserConfigManager = class {
434
+ configPath;
435
+ constructor() {
436
+ const configDir = path4.join(os.homedir(), ".team-cli");
437
+ this.configPath = path4.join(configDir, "config.json");
438
+ }
439
+ /**
440
+ * 加载用户配置
441
+ */
442
+ async load() {
443
+ try {
444
+ const exists = await FileUtils.exists(this.configPath);
445
+ if (!exists) {
446
+ return null;
447
+ }
448
+ const content = await FileUtils.read(this.configPath);
449
+ const config = JSON.parse(content);
450
+ if (config.gitlab?.accessToken) {
451
+ config.gitlab.accessToken = this.decrypt(config.gitlab.accessToken);
452
+ }
453
+ return config;
454
+ } catch (error) {
455
+ logger.debug(`\u52A0\u8F7D\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
456
+ return null;
457
+ }
458
+ }
459
+ /**
460
+ * 保存用户配置
461
+ */
462
+ async save(config) {
463
+ try {
464
+ const configDir = path4.dirname(this.configPath);
465
+ await FileUtils.ensureDir(configDir);
466
+ const configToSave = { ...config };
467
+ if (configToSave.gitlab?.accessToken) {
468
+ configToSave.gitlab.accessToken = this.encrypt(configToSave.gitlab.accessToken);
469
+ }
470
+ const content = JSON.stringify(configToSave, null, 2);
471
+ await FileUtils.write(this.configPath, content);
472
+ } catch (error) {
473
+ throw new Error(`\u4FDD\u5B58\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
474
+ }
475
+ }
476
+ /**
477
+ * 更新 GitLab Token
478
+ */
479
+ async updateGitLabToken(token, baseUrl) {
480
+ const config = await this.load() || {
481
+ gitlab: {
482
+ accessToken: "",
483
+ baseUrl: "https://gitlab.com",
484
+ timeout: 3e4
485
+ }
486
+ };
487
+ config.gitlab.accessToken = token;
488
+ if (baseUrl) {
489
+ config.gitlab.baseUrl = baseUrl;
490
+ }
491
+ await this.save(config);
492
+ }
493
+ /**
494
+ * 获取 GitLab Token
495
+ */
496
+ async getGitLabToken() {
497
+ const config = await this.load();
498
+ return config?.gitlab?.accessToken || null;
499
+ }
500
+ /**
501
+ * 获取 GitLab 配置
502
+ */
503
+ async getGitLabConfig() {
504
+ const config = await this.load();
505
+ return config?.gitlab || null;
506
+ }
507
+ /**
508
+ * 检查是否已有配置
509
+ */
510
+ async hasConfig() {
511
+ const config = await this.load();
512
+ return config !== null && config.gitlab?.accessToken !== void 0;
513
+ }
514
+ /**
515
+ * 删除配置
516
+ */
517
+ async removeConfig() {
518
+ try {
519
+ const exists = await FileUtils.exists(this.configPath);
520
+ if (exists) {
521
+ await FileUtils.remove(this.configPath);
522
+ }
523
+ } catch (error) {
524
+ throw new Error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error}`);
525
+ }
526
+ }
527
+ /**
528
+ * 简单加密 (使用机器特定密钥)
529
+ */
530
+ encrypt(text) {
531
+ const key = this.getMachineKey();
532
+ const iv = crypto.randomBytes(16);
533
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
534
+ let encrypted = cipher.update(text, "utf8", "hex");
535
+ encrypted += cipher.final("hex");
536
+ return iv.toString("hex") + ":" + encrypted;
537
+ }
538
+ /**
539
+ * 简单解密
540
+ */
541
+ decrypt(encryptedText) {
542
+ try {
543
+ const key = this.getMachineKey();
544
+ const parts = encryptedText.split(":");
545
+ const iv = Buffer.from(parts[0], "hex");
546
+ const encrypted = parts[1];
547
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
548
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
549
+ decrypted += decipher.final("utf8");
550
+ return decrypted;
551
+ } catch {
552
+ return encryptedText;
553
+ }
554
+ }
555
+ /**
556
+ * 获取机器特定密钥
557
+ */
558
+ getMachineKey() {
559
+ const hostname = os.hostname();
560
+ const platform = os.platform();
561
+ const arch = os.arch();
562
+ const cpus = os.cpus();
563
+ const machineInfo = `${hostname}-${platform}-${arch}-${cpus[0]?.model || "unknown"}`;
564
+ return crypto.createHash("sha256").update(machineInfo).digest();
565
+ }
566
+ /**
567
+ * 获取配置目录
568
+ */
569
+ getConfigDir() {
570
+ return path4.dirname(this.configPath);
571
+ }
572
+ /**
573
+ * 获取配置文件路径
574
+ */
575
+ getConfigPath() {
576
+ return this.configPath;
577
+ }
578
+ };
579
+ userConfigManager = new UserConfigManager();
153
580
  }
154
- /**
155
- * 打印带标签的文本
156
- */
157
- labeled(label, text, labelColor = "blue") {
158
- const coloredLabel = chalk[labelColor](`[${label}]`);
159
- console.log(`${coloredLabel} ${text}`);
581
+ });
582
+
583
+ // src/lib/gitlab-api.ts
584
+ var gitlab_api_exports = {};
585
+ __export(gitlab_api_exports, {
586
+ GitLabAPI: () => GitLabAPI
587
+ });
588
+ var GitLabAPI;
589
+ var init_gitlab_api = __esm({
590
+ "src/lib/gitlab-api.ts"() {
591
+ "use strict";
592
+ init_esm_shims();
593
+ GitLabAPI = class {
594
+ config;
595
+ defaultTimeout = 3e4;
596
+ constructor(config) {
597
+ this.config = {
598
+ ...config,
599
+ timeout: config.timeout || this.defaultTimeout
600
+ };
601
+ }
602
+ /**
603
+ * 验证 Token 是否有效
604
+ */
605
+ async authenticate() {
606
+ try {
607
+ const response = await this.request("/user");
608
+ return response.ok;
609
+ } catch (error) {
610
+ return false;
611
+ }
612
+ }
613
+ /**
614
+ * 列出项目的所有 Tags
615
+ */
616
+ async listTags(projectPath) {
617
+ try {
618
+ const encodedPath = this.encodeProjectPath(projectPath);
619
+ const response = await this.request(`/projects/${encodedPath}/repository/tags?per_page=100`);
620
+ if (!response.ok) {
621
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
622
+ }
623
+ const tags = await response.json();
624
+ return tags.sort((a, b) => {
625
+ return this.compareVersionStrings(b.name, a.name);
626
+ });
627
+ } catch (error) {
628
+ throw new Error(`\u83B7\u53D6 Tags \u5931\u8D25: ${error}`);
629
+ }
630
+ }
631
+ /**
632
+ * 列出项目的所有 Branches
633
+ */
634
+ async listBranches(projectPath) {
635
+ try {
636
+ const encodedPath = this.encodeProjectPath(projectPath);
637
+ const response = await this.request(`/projects/${encodedPath}/repository/branches?per_page=100`);
638
+ if (!response.ok) {
639
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
640
+ }
641
+ const branches = await response.json();
642
+ return branches.sort((a, b) => {
643
+ if (a.default) return -1;
644
+ if (b.default) return 1;
645
+ return a.name.localeCompare(b.name);
646
+ });
647
+ } catch (error) {
648
+ throw new Error(`\u83B7\u53D6 Branches \u5931\u8D25: ${error}`);
649
+ }
650
+ }
651
+ /**
652
+ * 验证 Tag 是否存在
653
+ */
654
+ async validateTag(projectPath, tag) {
655
+ try {
656
+ const tags = await this.listTags(projectPath);
657
+ return tags.some((t) => t.name === tag);
658
+ } catch {
659
+ return false;
660
+ }
661
+ }
662
+ /**
663
+ * 验证 Branch 是否存在
664
+ */
665
+ async validateBranch(projectPath, branch) {
666
+ try {
667
+ const branches = await this.listBranches(projectPath);
668
+ return branches.some((b) => b.name === branch);
669
+ } catch {
670
+ return false;
671
+ }
672
+ }
673
+ /**
674
+ * 获取项目信息
675
+ */
676
+ async getProject(projectPath) {
677
+ try {
678
+ const encodedPath = this.encodeProjectPath(projectPath);
679
+ const response = await this.request(`/projects/${encodedPath}`);
680
+ if (!response.ok) {
681
+ return null;
682
+ }
683
+ return await response.json();
684
+ } catch {
685
+ return null;
686
+ }
687
+ }
688
+ /**
689
+ * 获取指定 Tag 的 commit 信息
690
+ */
691
+ async getTagCommit(projectPath, tag) {
692
+ try {
693
+ const tags = await this.listTags(projectPath);
694
+ const targetTag = tags.find((t) => t.name === tag);
695
+ return targetTag?.commit.id || null;
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
700
+ /**
701
+ * 获取指定 Branch 的最新 commit 信息
702
+ */
703
+ async getBranchCommit(projectPath, branch) {
704
+ try {
705
+ const encodedPath = this.encodeProjectPath(projectPath);
706
+ const response = await this.request(
707
+ `/projects/${encodedPath}/repository/branches/${encodeURIComponent(branch)}`
708
+ );
709
+ if (!response.ok) {
710
+ return null;
711
+ }
712
+ const branchInfo = await response.json();
713
+ return branchInfo.commit.id;
714
+ } catch {
715
+ return null;
716
+ }
717
+ }
718
+ /**
719
+ * 对比两个版本之间的差异
720
+ */
721
+ async compareVersions(projectPath, from, to) {
722
+ try {
723
+ const encodedPath = this.encodeProjectPath(projectPath);
724
+ const response = await this.request(
725
+ `/projects/${encodedPath}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
726
+ );
727
+ if (!response.ok) {
728
+ return null;
729
+ }
730
+ return await response.json();
731
+ } catch {
732
+ return null;
733
+ }
734
+ }
735
+ /**
736
+ * 比较两个版本号(用于排序)
737
+ * 返回值: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
738
+ */
739
+ compareVersionStrings(v1, v2) {
740
+ const version1 = v1.replace(/^v/, "");
741
+ const version2 = v2.replace(/^v/, "");
742
+ const parts1 = version1.split(".").map((p) => parseInt(p, 10) || 0);
743
+ const parts2 = version2.split(".").map((p) => parseInt(p, 10) || 0);
744
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
745
+ const p1 = parts1[i] || 0;
746
+ const p2 = parts2[i] || 0;
747
+ if (p1 > p2) return 1;
748
+ if (p1 < p2) return -1;
749
+ }
750
+ return 0;
751
+ }
752
+ /**
753
+ * 获取最新的版本(优先 Tag,否则 latest commit)
754
+ */
755
+ async getLatestVersion(projectPath) {
756
+ try {
757
+ const tags = await this.listTags(projectPath);
758
+ const branches = await this.listBranches(projectPath);
759
+ if (tags.length > 0) {
760
+ const latestTag = tags[0];
761
+ return {
762
+ type: "tag",
763
+ name: latestTag.name,
764
+ commit: latestTag.commit.id
765
+ };
766
+ }
767
+ const defaultBranch = branches.find((b) => b.default);
768
+ if (defaultBranch) {
769
+ return {
770
+ type: "commit",
771
+ name: defaultBranch.name,
772
+ commit: defaultBranch.commit.id
773
+ };
774
+ }
775
+ return null;
776
+ } catch {
777
+ return null;
778
+ }
779
+ }
780
+ /**
781
+ * 解析项目路径
782
+ * 从 Git URL 中提取项目路径
783
+ */
784
+ static parseProjectPath(repository) {
785
+ let path17 = repository;
786
+ path17 = path17.replace(/^https?:\/\//, "");
787
+ path17 = path17.replace(/^git@/, "");
788
+ const parts = path17.split("/");
789
+ if (parts.length > 1) {
790
+ path17 = parts.slice(1).join("/");
791
+ }
792
+ path17 = path17.replace(/\.git$/, "");
793
+ return path17;
794
+ }
795
+ /**
796
+ * 编码项目路径用于 API 请求
797
+ */
798
+ encodeProjectPath(projectPath) {
799
+ return encodeURIComponent(projectPath).replace(/%2F/g, "%2F");
800
+ }
801
+ /**
802
+ * 发送 HTTP 请求
803
+ */
804
+ async request(endpoint, options = {}) {
805
+ const url = `${this.config.baseUrl}/api/v4${endpoint}`;
806
+ const headers = {
807
+ "PRIVATE-TOKEN": this.config.accessToken,
808
+ "Content-Type": "application/json",
809
+ ...options.headers
810
+ };
811
+ const controller = new AbortController();
812
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
813
+ try {
814
+ const response = await fetch(url, {
815
+ ...options,
816
+ headers,
817
+ signal: controller.signal
818
+ });
819
+ return response;
820
+ } catch (error) {
821
+ if (error.name === "AbortError") {
822
+ throw new Error("\u8BF7\u6C42\u8D85\u65F6");
823
+ }
824
+ throw error;
825
+ } finally {
826
+ clearTimeout(timeoutId);
827
+ }
828
+ }
829
+ /**
830
+ * 获取默认分支
831
+ */
832
+ async getDefaultBranch(projectPath) {
833
+ try {
834
+ const project = await this.getProject(projectPath);
835
+ return project?.default_branch || null;
836
+ } catch {
837
+ return null;
838
+ }
839
+ }
840
+ };
160
841
  }
161
- };
162
- var logger = new Logger();
842
+ });
843
+
844
+ // src/index.ts
845
+ init_esm_shims();
846
+ init_logger();
847
+ import { Command as Command16 } from "commander";
848
+ import chalk4 from "chalk";
163
849
 
164
850
  // src/commands/init.ts
851
+ init_esm_shims();
852
+ init_logger();
165
853
  import { Command } from "commander";
166
854
  import inquirer from "inquirer";
167
- import path2 from "path";
855
+ import path5 from "path";
168
856
  import fs2 from "fs-extra";
169
857
 
170
858
  // src/lib/claude.ts
859
+ init_esm_shims();
860
+ init_logger();
861
+ init_utils();
171
862
  import { execa } from "execa";
172
863
  var ClaudeAI = class {
173
864
  verbose;
@@ -202,25 +893,45 @@ var ClaudeAI = class {
202
893
  async prompt(promptText, options) {
203
894
  const spinner = logger.startLoading("\u6B63\u5728\u8C03\u7528 Claude...");
204
895
  try {
205
- const args = ["--no-confirm", "-p", promptText];
896
+ const args = [];
897
+ const validContextFiles = [];
206
898
  if (options?.contextFiles && options.contextFiles.length > 0) {
207
899
  for (const file of options.contextFiles) {
208
- args.unshift("--context", file);
900
+ const exists = await FileUtils.exists(file);
901
+ if (exists) {
902
+ validContextFiles.push(file);
903
+ } else {
904
+ logger.warn(`\u4E0A\u4E0B\u6587\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${file}`);
905
+ }
906
+ }
907
+ for (const file of validContextFiles) {
908
+ args.push("--context", file);
209
909
  }
210
910
  }
211
- const { stdout } = await execa("claude", args, {
911
+ args.push("--no-confirm", "-p", promptText);
912
+ const result = await execa("claude", args, {
212
913
  stdio: this.verbose ? "inherit" : "pipe",
213
- timeout: options?.timeout || 3e5
914
+ timeout: options?.timeout || 3e5,
214
915
  // 默认 5 分钟
916
+ reject: false
917
+ // 不自动拒绝非零退出码
215
918
  });
216
919
  spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
217
- return stdout;
920
+ if (result.exitCode !== 0 && !result.stdout) {
921
+ const stderr = result.stderr || "";
922
+ throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})${stderr ? `
923
+ ${stderr}` : ""}`);
924
+ }
925
+ return result.stdout || "";
218
926
  } catch (error) {
219
927
  spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
220
928
  if (error.killed && error.signal === "SIGTERM") {
221
929
  throw new Error("Claude \u6267\u884C\u8D85\u65F6");
222
930
  }
223
- throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}`);
931
+ const stderr = error.stderr || "";
932
+ const exitCode = error.exitCode !== void 0 ? ` (\u9000\u51FA\u7801: ${error.exitCode})` : "";
933
+ throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}${exitCode}${stderr ? `
934
+ ${stderr}` : ""}`);
224
935
  }
225
936
  }
226
937
  /**
@@ -244,12 +955,17 @@ var ClaudeAI = class {
244
955
  const role = msg.role === "system" ? "\u7CFB\u7EDF\u6307\u4EE4" : `${msg.role === "user" ? "\u7528\u6237" : "\u52A9\u624B"}`;
245
956
  return `[${role}]: ${msg.content}`;
246
957
  }).join("\n\n");
247
- const { stdout } = await execa("claude", ["--no-confirm", "-p", fullPrompt], {
958
+ const args = ["--no-confirm", "-p", fullPrompt];
959
+ const result = await execa("claude", args, {
248
960
  stdio: this.verbose ? "inherit" : "pipe",
249
- timeout: options?.timeout || 3e5
961
+ timeout: options?.timeout || 3e5,
962
+ reject: false
250
963
  });
251
964
  spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
252
- return stdout;
965
+ if (result.exitCode !== 0 && !result.stdout) {
966
+ throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})`);
967
+ }
968
+ return result.stdout || "";
253
969
  } catch (error) {
254
970
  spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
255
971
  throw error;
@@ -460,234 +1176,135 @@ ${projectContext}
460
1176
  };
461
1177
  var claudeAI = new ClaudeAI();
462
1178
 
463
- // src/lib/utils.ts
464
- import fs from "fs-extra";
465
- import path from "path";
466
- import { glob } from "glob";
467
- var FileUtils = class {
468
- /**
469
- * 确保目录存在
470
- */
471
- static async ensureDir(dir) {
472
- await fs.ensureDir(dir);
473
- }
474
- /**
475
- * 读取文件内容
476
- */
477
- static async read(file) {
478
- return await fs.readFile(file, "utf-8");
479
- }
480
- /**
481
- * 写入文件内容
482
- */
483
- static async write(file, content) {
484
- await fs.writeFile(file, content, "utf-8");
485
- }
486
- /**
487
- * 检查文件是否存在
488
- */
489
- static async exists(file) {
490
- return await fs.pathExists(file);
491
- }
492
- /**
493
- * 复制文件
494
- */
495
- static async copy(src, dest) {
496
- await fs.copy(src, dest);
497
- }
498
- /**
499
- * 删除文件或目录
500
- */
501
- static async remove(file) {
502
- await fs.remove(file);
503
- }
504
- /**
505
- * 移动文件
506
- */
507
- static async move(src, dest) {
508
- await fs.move(src, dest);
509
- }
510
- /**
511
- * 使用 glob 查找文件
512
- */
513
- static async findFiles(pattern, cwd) {
514
- return await glob(pattern, {
515
- cwd,
516
- absolute: false,
517
- nodir: true
518
- });
519
- }
520
- /**
521
- * 读取 JSON 文件
522
- */
523
- static async readJson(file) {
524
- return await fs.readJson(file);
525
- }
526
- /**
527
- * 写入 JSON 文件
528
- */
529
- static async writeJson(file, data) {
530
- await fs.writeJson(file, data, { spaces: 2 });
1179
+ // src/commands/init.ts
1180
+ init_utils();
1181
+ import { Listr } from "listr2";
1182
+
1183
+ // src/lib/template-version.ts
1184
+ init_esm_shims();
1185
+ init_utils();
1186
+ init_logger();
1187
+ import { execa as execa2 } from "execa";
1188
+ import path3 from "path";
1189
+ var DEFAULT_TEMPLATES = {
1190
+ frontend: {
1191
+ repository: "https://gitlab.yungu-inc.org/static/fe-tpl-ai"
1192
+ },
1193
+ backend: {
1194
+ repository: "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git"
531
1195
  }
532
1196
  };
533
- var StringUtils2 = class {
534
- /**
535
- * 转换为 kebab-case
536
- */
537
- static toKebabCase(str) {
538
- return str.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
539
- }
540
- /**
541
- * 转换为 PascalCase
542
- */
543
- static toPascalCase(str) {
544
- return str.replace(/[-_\s](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
545
- }
546
- /**
547
- * 转换为 camelCase
548
- */
549
- static toCamelCase(str) {
550
- const pascal = this.toPascalCase(str);
551
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
552
- }
553
- /**
554
- * 转换为 snake_case
555
- */
556
- static toSnakeCase(str) {
557
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/^_/, "").replace(/-+/g, "_").replace(/[\s]+/g, "_");
558
- }
559
- /**
560
- * 首字母大写
561
- */
562
- static capitalize(str) {
563
- return str.charAt(0).toUpperCase() + str.slice(1);
1197
+ function getConfigPath(projectPath) {
1198
+ return path3.join(projectPath, ".team-cli", "template.json");
1199
+ }
1200
+ async function readTemplateConfig(projectPath) {
1201
+ const configPath = getConfigPath(projectPath);
1202
+ const exists = await FileUtils.exists(configPath);
1203
+ if (!exists) {
1204
+ return null;
564
1205
  }
565
- /**
566
- * 截断字符串
567
- */
568
- static truncate(str, length, suffix = "...") {
569
- if (str.length <= length) return str;
570
- return str.slice(0, length - suffix.length) + suffix;
1206
+ try {
1207
+ const content = await FileUtils.read(configPath);
1208
+ return JSON.parse(content);
1209
+ } catch {
1210
+ return null;
571
1211
  }
572
- /**
573
- * 生成 slug
574
- */
575
- static slugify(str) {
576
- return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
1212
+ }
1213
+ async function saveTemplateConfig(projectPath, config) {
1214
+ const configDir = path3.join(projectPath, ".team-cli");
1215
+ await FileUtils.ensureDir(configDir);
1216
+ const configPath = getConfigPath(projectPath);
1217
+ const content = JSON.stringify(config, null, 2);
1218
+ await FileUtils.write(configPath, content);
1219
+ }
1220
+ async function initTemplateConfig(projectPath) {
1221
+ const config = await readTemplateConfig(projectPath);
1222
+ if (config) {
1223
+ return;
577
1224
  }
578
- };
579
- var DateUtils = class _DateUtils {
580
- /**
581
- * 格式化日期
582
- */
583
- static format(date = /* @__PURE__ */ new Date(), format = "YYYY-MM-DD HH:mm:ss") {
584
- const year = date.getFullYear();
585
- const month = String(date.getMonth() + 1).padStart(2, "0");
586
- const day = String(date.getDate()).padStart(2, "0");
587
- const hours = String(date.getHours()).padStart(2, "0");
588
- const minutes = String(date.getMinutes()).padStart(2, "0");
589
- const seconds = String(date.getSeconds()).padStart(2, "0");
590
- return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
1225
+ await saveTemplateConfig(projectPath, DEFAULT_TEMPLATES);
1226
+ }
1227
+ async function getLatestCommit(repository) {
1228
+ try {
1229
+ const { stdout } = await execa2("git", ["ls-remote", repository, "HEAD"], {
1230
+ stdio: "pipe"
1231
+ });
1232
+ const commit = stdout.split(" ")[0];
1233
+ return commit;
1234
+ } catch (error) {
1235
+ logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 commit \u5931\u8D25: ${error}`);
1236
+ return "";
591
1237
  }
592
- /**
593
- * 获取相对时间
594
- */
595
- static relative(date) {
596
- const now = /* @__PURE__ */ new Date();
597
- const diff = now.getTime() - date.getTime();
598
- const seconds = Math.floor(diff / 1e3);
599
- const minutes = Math.floor(seconds / 60);
600
- const hours = Math.floor(minutes / 60);
601
- const days = Math.floor(hours / 24);
602
- if (days > 7) {
603
- return _DateUtils.format(date, "YYYY-MM-DD");
604
- } else if (days > 0) {
605
- return `${days} \u5929\u524D`;
606
- } else if (hours > 0) {
607
- return `${hours} \u5C0F\u65F6\u524D`;
608
- } else if (minutes > 0) {
609
- return `${minutes} \u5206\u949F\u524D`;
610
- } else {
611
- return "\u521A\u521A";
612
- }
1238
+ }
1239
+ async function getLatestTag(repository) {
1240
+ try {
1241
+ const { stdout } = await execa2(
1242
+ "git",
1243
+ ["ls-remote", "--tags", repository],
1244
+ {
1245
+ stdio: "pipe"
1246
+ }
1247
+ );
1248
+ const tags = stdout.split("\n").filter((line) => line.includes("refs/tags/") && !line.includes("^{}")).map((line) => {
1249
+ const [, ref] = line.split(" ");
1250
+ return ref.replace("refs/tags/", "");
1251
+ }).filter((tag) => /^v?\d+\.\d+\.\d+/.test(tag)).sort((a, b) => {
1252
+ const va = a.replace(/^v/, "").split(".").map(Number);
1253
+ const vb = b.replace(/^v/, "").split(".").map(Number);
1254
+ for (let i = 0; i < 3; i++) {
1255
+ if ((va[i] || 0) > (vb[i] || 0)) return 1;
1256
+ if ((va[i] || 0) < (vb[i] || 0)) return -1;
1257
+ }
1258
+ return 0;
1259
+ });
1260
+ return tags[tags.length - 1] || "";
1261
+ } catch (error) {
1262
+ logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 tag \u5931\u8D25: ${error}`);
1263
+ return "";
613
1264
  }
614
- };
615
- var GitUtils = class {
616
- /**
617
- * 检查是否在 Git 仓库中
618
- */
619
- static async isGitRepo(cwd = process.cwd()) {
620
- const gitDir = path.join(cwd, ".git");
621
- return await FileUtils.exists(gitDir);
1265
+ }
1266
+ async function checkTemplateUpdate(projectPath, type) {
1267
+ const config = await readTemplateConfig(projectPath);
1268
+ if (!config || !config[type]) {
1269
+ return null;
622
1270
  }
623
- /**
624
- * 获取当前分支名
625
- */
626
- static async getCurrentBranch(cwd = process.cwd()) {
627
- const { execa: execa3 } = await import("execa");
628
- const { stdout } = await execa3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
629
- cwd
630
- });
631
- return stdout.trim();
1271
+ const repository = config[type].repository;
1272
+ const currentCommit = config[type].commit;
1273
+ const currentTag = config[type].tag;
1274
+ const latestCommit = await getLatestCommit(repository);
1275
+ const latestTag = await getLatestTag(repository);
1276
+ if (!latestCommit) {
1277
+ return null;
632
1278
  }
633
- /**
634
- * 获取当前 commit hash
635
- */
636
- static async getCurrentCommit(cwd = process.cwd()) {
637
- const { execa: execa3 } = await import("execa");
638
- const { stdout } = await execa3("git", ["rev-parse", "HEAD"], { cwd });
639
- return stdout.trim().slice(0, 7);
1279
+ const needsUpdate = currentCommit && currentCommit !== latestCommit;
1280
+ return {
1281
+ type,
1282
+ repository,
1283
+ currentCommit,
1284
+ currentTag,
1285
+ latestCommit,
1286
+ latestTag,
1287
+ needsUpdate: needsUpdate || false
1288
+ };
1289
+ }
1290
+ async function updateTemplateVersion(projectPath, type, commit, options) {
1291
+ const config = await readTemplateConfig(projectPath);
1292
+ if (!config) {
1293
+ return;
640
1294
  }
641
- };
642
- var SpecUtils = class {
643
- /**
644
- * 解析 spec 文件
645
- */
646
- static async parseSpec(file) {
647
- const content = await FileUtils.read(file);
648
- const spec = {};
649
- const titleMatch = content.match(/^#\s+(.+)$/m);
650
- if (titleMatch) {
651
- spec.title = titleMatch[1];
652
- }
653
- const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
654
- if (nameMatch) {
655
- spec.name = nameMatch[1];
656
- }
657
- const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
658
- if (priorityMatch) {
659
- spec.priority = priorityMatch[1];
660
- }
661
- const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
662
- if (statusMatch) {
663
- spec.status = statusMatch[1];
664
- }
665
- const depSection = content.match(/## 依赖关系\s+([\s\S]+?)##/m);
666
- if (depSection) {
667
- const depMatches = depSection[1].matchAll(/- \[([ x])\]\s*(.+)$/gm);
668
- spec.dependencies = Array.from(depMatches).map((m) => ({
669
- completed: m[1] === "x",
670
- name: m[2]
671
- }));
672
- }
673
- return spec;
1295
+ config[type].commit = commit;
1296
+ if (options?.tag) {
1297
+ config[type].tag = options.tag;
674
1298
  }
675
- /**
676
- * 获取 spec 状态
677
- */
678
- static async getSpecStatus(file) {
679
- try {
680
- const spec = await this.parseSpec(file);
681
- return spec.status || "\u672A\u77E5";
682
- } catch {
683
- return "\u9519\u8BEF";
684
- }
1299
+ if (options?.branch) {
1300
+ config[type].branch = options.branch;
685
1301
  }
686
- };
1302
+ config[type].lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
1303
+ await saveTemplateConfig(projectPath, config);
1304
+ }
687
1305
 
688
1306
  // src/commands/init.ts
689
- import { Listr } from "listr2";
690
- var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u540D\u79F0").description("\u521D\u59CB\u5316\u65B0\u9879\u76EE").option("-d, --dir <directory>", "\u9879\u76EE\u76EE\u5F55", ".").option("--no-docker", "\u4E0D\u751F\u6210 Docker \u914D\u7F6E").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").action(async (projectName, options) => {
1307
+ var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u540D\u79F0").description("\u521D\u59CB\u5316\u65B0\u9879\u76EE").option("-d, --dir <directory>", "\u9879\u76EE\u76EE\u5F55", ".").option("--no-docker", "\u4E0D\u751F\u6210 Docker \u914D\u7F6E").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").option("--tag <tag>", "\u6307\u5B9A\u6A21\u677F\u6807\u7B7E (\u524D\u540E\u7AEF\u901A\u7528)").option("--backend-tag <tag>", "\u6307\u5B9A\u540E\u7AEF\u6A21\u677F\u6807\u7B7E").option("--frontend-tag <tag>", "\u6307\u5B9A\u524D\u7AEF\u6A21\u677F\u6807\u7B7E").option("--backend-branch <branch>", "\u6307\u5B9A\u540E\u7AEF\u6A21\u677F\u5206\u652F").option("--frontend-branch <branch>", "\u6307\u5B9A\u524D\u7AEF\u6A21\u677F\u5206\u652F").action(async (projectName, options) => {
691
1308
  try {
692
1309
  if (!projectName) {
693
1310
  const answers = await inquirer.prompt([
@@ -718,7 +1335,7 @@ var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u
718
1335
  logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
719
1336
  process.exit(1);
720
1337
  }
721
- const projectPath = path2.resolve(options.dir, projectName);
1338
+ const projectPath = path5.resolve(options.dir, projectName);
722
1339
  if (await FileUtils.exists(projectPath)) {
723
1340
  logger.error(`\u76EE\u5F55\u5DF2\u5B58\u5728: ${projectPath}`);
724
1341
  logger.info("\u8BF7\u9009\u62E9\u5176\u4ED6\u9879\u76EE\u540D\u79F0\u6216\u5220\u9664\u73B0\u6709\u76EE\u5F55");
@@ -737,11 +1354,11 @@ var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u
737
1354
  title: "\u521B\u5EFA\u9879\u76EE\u76EE\u5F55",
738
1355
  task: async () => {
739
1356
  await FileUtils.ensureDir(projectPath);
740
- await FileUtils.ensureDir(path2.join(projectPath, "frontend"));
741
- await FileUtils.ensureDir(path2.join(projectPath, "backend"));
742
- await FileUtils.ensureDir(path2.join(projectPath, "docs/specs"));
743
- await FileUtils.ensureDir(path2.join(projectPath, "docs/api"));
744
- await FileUtils.ensureDir(path2.join(projectPath, "docs/sessions"));
1357
+ await FileUtils.ensureDir(path5.join(projectPath, "frontend"));
1358
+ await FileUtils.ensureDir(path5.join(projectPath, "backend"));
1359
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/specs"));
1360
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/api"));
1361
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/sessions"));
745
1362
  }
746
1363
  },
747
1364
  {
@@ -771,7 +1388,12 @@ var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u
771
1388
  {
772
1389
  title: "\u514B\u9686\u540E\u7AEF\u6A21\u677F",
773
1390
  task: async () => {
774
- await cloneBackendTemplate(projectPath);
1391
+ const backendTag = options.backendTag || options.tag;
1392
+ const backendBranch = options.backendBranch;
1393
+ await cloneBackendTemplate(projectPath, {
1394
+ tag: backendTag,
1395
+ branch: backendBranch
1396
+ });
775
1397
  }
776
1398
  },
777
1399
  {
@@ -916,7 +1538,7 @@ docs/
916
1538
  \u2514\u2500\u2500 sessions/ # \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
917
1539
  \`\`\`
918
1540
  `;
919
- await FileUtils.write(path2.join(projectPath, "TECH_STACK.md"), content);
1541
+ await FileUtils.write(path5.join(projectPath, "TECH_STACK.md"), content);
920
1542
  }
921
1543
  async function generateConventions(projectPath) {
922
1544
  const content = `# \u5F00\u53D1\u89C4\u8303
@@ -1219,7 +1841,7 @@ test('renders user name', () => {
1219
1841
  });
1220
1842
  \`\`\`
1221
1843
  `;
1222
- await FileUtils.write(path2.join(projectPath, "CONVENTIONS.md"), content);
1844
+ await FileUtils.write(path5.join(projectPath, "CONVENTIONS.md"), content);
1223
1845
  }
1224
1846
  async function generateAIMemory(projectPath, projectName) {
1225
1847
  const content = `# AI Memory - \u9879\u76EE\u72B6\u6001\u8BB0\u5F55
@@ -1230,6 +1852,15 @@ async function generateAIMemory(projectPath, projectName) {
1230
1852
  - **\u5F53\u524D\u9636\u6BB5**: \u521D\u59CB\u5316
1231
1853
  - **\u6700\u540E\u66F4\u65B0**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
1232
1854
 
1855
+ ## \u6A21\u677F\u7248\u672C\u4FE1\u606F
1856
+
1857
+ > \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u7BA1\u7406\uFF0C\u8BB0\u5F55\u4F7F\u7528\u7684\u524D\u540E\u7AEF\u6A21\u677F\u7248\u672C
1858
+
1859
+ | \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |
1860
+ |------|------|-----|--------|--------|----------|
1861
+ | \u524D\u7AEF | - | - | - | - | - |
1862
+ | \u540E\u7AEF | - | - | - | - | - |
1863
+
1233
1864
  ## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
1234
1865
 
1235
1866
  | \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
@@ -1269,7 +1900,7 @@ async function generateAIMemory(projectPath, projectName) {
1269
1900
  | Bug ID | \u65E5\u671F | \u95EE\u9898\u63CF\u8FF0 | \u72B6\u6001 |
1270
1901
  |--------|------|---------|------|
1271
1902
  `;
1272
- await FileUtils.write(path2.join(projectPath, "AI_MEMORY.md"), content);
1903
+ await FileUtils.write(path5.join(projectPath, "AI_MEMORY.md"), content);
1273
1904
  }
1274
1905
  async function generateSpecTemplate(projectPath) {
1275
1906
  const content = `# [\u529F\u80FD\u6807\u9898]
@@ -1333,35 +1964,78 @@ async function generateSpecTemplate(projectPath) {
1333
1964
  ----
1334
1965
  *\u751F\u6210\u4E8E: {{TIMESTAMP}} by team-cli*
1335
1966
  `;
1336
- await FileUtils.write(path2.join(projectPath, "docs/specs/template.md"), content);
1967
+ await FileUtils.write(path5.join(projectPath, "docs/specs/template.md"), content);
1337
1968
  }
1338
- async function cloneBackendTemplate(projectPath) {
1969
+ async function cloneBackendTemplate(projectPath, versionOptions) {
1339
1970
  const templateRepo = process.env.TEMPLATE_REPO || "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git";
1340
- const backendPath = path2.join(projectPath, "backend");
1971
+ const backendPath = path5.join(projectPath, "backend");
1341
1972
  try {
1342
1973
  const { execaCommand } = await import("execa");
1343
- const tempDir = path2.join(projectPath, ".template-temp");
1344
- await execaCommand(`git clone --depth=1 ${templateRepo} ${tempDir}`, {
1974
+ const tempDir = path5.join(projectPath, ".template-temp");
1975
+ if (versionOptions?.tag || versionOptions?.branch) {
1976
+ const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
1977
+ const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
1978
+ const config = await userConfigManager2.getGitLabConfig();
1979
+ if (config) {
1980
+ const gitlabAPI = new GitLabAPI2(config);
1981
+ const projectPathEncoded = GitLabAPI2.parseProjectPath(templateRepo);
1982
+ if (versionOptions.tag) {
1983
+ const isValid = await gitlabAPI.validateTag(projectPathEncoded, versionOptions.tag);
1984
+ if (!isValid) {
1985
+ logger.error(`\u540E\u7AEF\u6A21\u677F tag "${versionOptions.tag}" \u4E0D\u5B58\u5728`);
1986
+ process.exit(1);
1987
+ }
1988
+ logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F tag: ${versionOptions.tag}`);
1989
+ }
1990
+ if (versionOptions.branch) {
1991
+ const isValid = await gitlabAPI.validateBranch(projectPathEncoded, versionOptions.branch);
1992
+ if (!isValid) {
1993
+ logger.error(`\u540E\u7AEF\u6A21\u677F\u5206\u652F "${versionOptions.branch}" \u4E0D\u5B58\u5728`);
1994
+ process.exit(1);
1995
+ }
1996
+ logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F\u5206\u652F: ${versionOptions.branch}`);
1997
+ }
1998
+ } else {
1999
+ logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
2000
+ }
2001
+ }
2002
+ const ref = versionOptions?.tag || versionOptions?.branch || "HEAD";
2003
+ await execaCommand(`git clone --depth=1 --branch ${ref} ${templateRepo} ${tempDir}`, {
1345
2004
  stdio: "inherit",
1346
2005
  timeout: 6e4
1347
2006
  });
2007
+ const { execa: e } = await import("execa");
2008
+ const { stdout: commit } = await e("git", ["rev-parse", "HEAD"], {
2009
+ cwd: tempDir,
2010
+ stdio: "pipe"
2011
+ });
2012
+ const { stdout: tags } = await e("git", ["tag", "-l", "--sort=-v:refname"], {
2013
+ cwd: tempDir,
2014
+ stdio: "pipe"
2015
+ });
2016
+ const latestTag = tags.split("\n")[0] || void 0;
1348
2017
  await fs2.copy(tempDir, backendPath, {
1349
2018
  filter: (src) => !src.includes(".git")
1350
2019
  });
1351
2020
  await fs2.remove(tempDir);
1352
- const gitDir = path2.join(backendPath, ".git");
2021
+ const gitDir = path5.join(backendPath, ".git");
1353
2022
  if (await FileUtils.exists(gitDir)) {
1354
2023
  await FileUtils.remove(gitDir);
1355
2024
  }
2025
+ await initTemplateConfig(projectPath);
2026
+ await updateTemplateVersion(projectPath, "backend", commit.trim(), {
2027
+ tag: versionOptions?.tag || latestTag,
2028
+ branch: versionOptions?.branch
2029
+ });
1356
2030
  } catch (error) {
1357
2031
  logger.warn("\u514B\u9686\u540E\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1358
- await FileUtils.ensureDir(path2.join(backendPath, "src/main/java/com/example"));
1359
- await FileUtils.ensureDir(path2.join(backendPath, "src/main/resources"));
1360
- await FileUtils.ensureDir(path2.join(backendPath, "src/test/java"));
2032
+ await FileUtils.ensureDir(path5.join(backendPath, "src/main/java/com/example"));
2033
+ await FileUtils.ensureDir(path5.join(backendPath, "src/main/resources"));
2034
+ await FileUtils.ensureDir(path5.join(backendPath, "src/test/java"));
1361
2035
  }
1362
2036
  }
1363
2037
  async function generateFrontendScaffold(projectPath) {
1364
- const frontendPath = path2.join(projectPath, "frontend");
2038
+ const frontendPath = path5.join(projectPath, "frontend");
1365
2039
  try {
1366
2040
  const prompt = `Read TECH_STACK.md and CONVENTIONS.md.
1367
2041
  Initialize a Next.js 14 frontend in ./frontend with:
@@ -1374,16 +2048,16 @@ Initialize a Next.js 14 frontend in ./frontend with:
1374
2048
  Do not run any servers, just generate the folder structure and configuration files.`;
1375
2049
  await claudeAI.prompt(prompt, {
1376
2050
  contextFiles: [
1377
- path2.join(projectPath, "TECH_STACK.md"),
1378
- path2.join(projectPath, "CONVENTIONS.md")
2051
+ path5.join(projectPath, "TECH_STACK.md"),
2052
+ path5.join(projectPath, "CONVENTIONS.md")
1379
2053
  ]
1380
2054
  });
1381
2055
  } catch (error) {
1382
2056
  logger.warn("Claude \u751F\u6210\u524D\u7AEF\u5931\u8D25\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1383
- await FileUtils.ensureDir(path2.join(frontendPath, "src/app"));
1384
- await FileUtils.ensureDir(path2.join(frontendPath, "src/components"));
1385
- await FileUtils.ensureDir(path2.join(frontendPath, "src/lib"));
1386
- await FileUtils.ensureDir(path2.join(frontendPath, "public"));
2057
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/app"));
2058
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/components"));
2059
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/lib"));
2060
+ await FileUtils.ensureDir(path5.join(frontendPath, "public"));
1387
2061
  }
1388
2062
  }
1389
2063
  async function generateDockerFiles(projectPath) {
@@ -1405,9 +2079,12 @@ async function initGit(projectPath, projectName) {
1405
2079
  }
1406
2080
 
1407
2081
  // src/commands/breakdown.ts
2082
+ init_esm_shims();
2083
+ init_utils();
2084
+ init_logger();
1408
2085
  import { Command as Command2 } from "commander";
1409
2086
  import inquirer2 from "inquirer";
1410
- import path3 from "path";
2087
+ import path6 from "path";
1411
2088
  import { Listr as Listr2 } from "listr2";
1412
2089
  var breakdownCommand = new Command2("breakdown").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos").action(async (specFile) => {
1413
2090
  try {
@@ -1453,9 +2130,9 @@ var breakdownCommand = new Command2("breakdown").argument("[spec-file]", "Spec \
1453
2130
  choices: ctx.specs
1454
2131
  }
1455
2132
  ]);
1456
- return path3.join("docs/specs", selectedFile);
2133
+ return path6.join("docs/specs", selectedFile);
1457
2134
  }
1458
- const fullPath = specFile.startsWith("docs/specs/") ? specFile : path3.join("docs/specs", specFile);
2135
+ const fullPath = specFile.startsWith("docs/specs/") ? specFile : path6.join("docs/specs", specFile);
1459
2136
  const exists = await FileUtils.exists(fullPath);
1460
2137
  if (!exists) {
1461
2138
  throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
@@ -1582,9 +2259,12 @@ Important Instructions:
1582
2259
  }
1583
2260
 
1584
2261
  // src/commands/dev.ts
2262
+ init_esm_shims();
2263
+ init_utils();
2264
+ init_logger();
1585
2265
  import { Command as Command3 } from "commander";
1586
2266
  import inquirer3 from "inquirer";
1587
- import path4 from "path";
2267
+ import path7 from "path";
1588
2268
  var devCommand = new Command3("dev").description("\u5F00\u53D1\u6A21\u5F0F\uFF0C\u6267\u884C\u5177\u4F53\u4EFB\u52A1").action(async () => {
1589
2269
  try {
1590
2270
  logger.header("\u5F00\u53D1\u6A21\u5F0F");
@@ -1629,7 +2309,7 @@ async function selectSpec() {
1629
2309
  }
1630
2310
  const specs = [];
1631
2311
  for (let i = 0; i < specFiles.length; i++) {
1632
- const file = path4.join(specDir, specFiles[i]);
2312
+ const file = path7.join(specDir, specFiles[i]);
1633
2313
  const spec = await FileUtils.read(file);
1634
2314
  const status = parseSpecStatus(spec);
1635
2315
  const dependencies = parseDependencies(spec);
@@ -1943,8 +2623,8 @@ async function generateSessionLog(specFile, milestone, todo, taskDescription, re
1943
2623
  const sessionDir = "docs/sessions";
1944
2624
  await FileUtils.ensureDir(sessionDir);
1945
2625
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1946
- const specName = path4.basename(specFile, ".md");
1947
- const logFile = path4.join(sessionDir, `${timestamp}_${specName}.md`);
2626
+ const specName = path7.basename(specFile, ".md");
2627
+ const logFile = path7.join(sessionDir, `${timestamp}_${specName}.md`);
1948
2628
  const content = `# \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
1949
2629
 
1950
2630
  **\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
@@ -1976,9 +2656,12 @@ ${result}
1976
2656
  }
1977
2657
 
1978
2658
  // src/commands/add-feature.ts
2659
+ init_esm_shims();
2660
+ init_utils();
2661
+ init_logger();
1979
2662
  import { Command as Command4 } from "commander";
1980
2663
  import inquirer4 from "inquirer";
1981
- import path5 from "path";
2664
+ import path8 from "path";
1982
2665
  import { Listr as Listr3 } from "listr2";
1983
2666
  var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "\u529F\u80FD\u540D\u79F0").description("\u6DFB\u52A0\u65B0\u529F\u80FD\uFF08\u652F\u6301 PRD \u6216\u7B80\u5355\u63CF\u8FF0\u6A21\u5F0F\uFF09").action(async (featureName) => {
1984
2667
  try {
@@ -1997,7 +2680,7 @@ var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "
1997
2680
  process.exit(1);
1998
2681
  }
1999
2682
  const featureSlug = StringUtils2.toKebabCase(featureName);
2000
- const specFile = path5.join("docs/specs", `${featureSlug}.md`);
2683
+ const specFile = path8.join("docs/specs", `${featureSlug}.md`);
2001
2684
  const specExists = await FileUtils.exists(specFile);
2002
2685
  if (specExists) {
2003
2686
  logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
@@ -2056,7 +2739,7 @@ async function addFeatureFromPrd(featureName, featureSlug, specFile) {
2056
2739
  const specs = files.filter((f) => !f.includes("template"));
2057
2740
  ctx2.completedSpecs = [];
2058
2741
  for (const file of specs) {
2059
- const status = await SpecUtils.getSpecStatus(path5.join(specDir, file));
2742
+ const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
2060
2743
  if (status === "\u5DF2\u5B8C\u6210") {
2061
2744
  ctx2.completedSpecs.push(file.replace(".md", ""));
2062
2745
  }
@@ -2128,7 +2811,7 @@ async function addFeatureSimple(featureName, featureSlug, specFile) {
2128
2811
  const specs = files.filter((f) => !f.includes("template"));
2129
2812
  ctx2.completedSpecs = [];
2130
2813
  for (const file of specs) {
2131
- const status = await SpecUtils.getSpecStatus(path5.join(specDir, file));
2814
+ const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
2132
2815
  if (status === "\u5DF2\u5B8C\u6210") {
2133
2816
  ctx2.completedSpecs.push(file.replace(".md", ""));
2134
2817
  }
@@ -2225,7 +2908,7 @@ async function buildProjectContext() {
2225
2908
  const files = await FileUtils.findFiles("*.md", "docs/specs");
2226
2909
  const specs = files.filter((f) => !f.includes("template"));
2227
2910
  for (const file of specs) {
2228
- const status = await SpecUtils.getSpecStatus(path5.join("docs/specs", file));
2911
+ const status = await SpecUtils.getSpecStatus(path8.join("docs/specs", file));
2229
2912
  context.push(` - ${file.replace(".md", "")} [${status}]`);
2230
2913
  }
2231
2914
  }
@@ -2465,8 +3148,11 @@ async function askToAdjust(specFile) {
2465
3148
  }
2466
3149
 
2467
3150
  // src/commands/split-prd.ts
3151
+ init_esm_shims();
3152
+ init_utils();
3153
+ init_logger();
2468
3154
  import { Command as Command5 } from "commander";
2469
- import path6 from "path";
3155
+ import path9 from "path";
2470
3156
  import { Listr as Listr4 } from "listr2";
2471
3157
  var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u6587\u6863\u76EE\u5F55").description("\u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs").action(async (prdFolder) => {
2472
3158
  try {
@@ -2496,7 +3182,7 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
2496
3182
  ctx2.prdFiles = [];
2497
3183
  for (const ext of supportedExtensions) {
2498
3184
  const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
2499
- ctx2.prdFiles.push(...files.map((f) => path6.join(prdFolder, f)));
3185
+ ctx2.prdFiles.push(...files.map((f) => path9.join(prdFolder, f)));
2500
3186
  }
2501
3187
  if (ctx2.prdFiles.length === 0) {
2502
3188
  throw new Error(
@@ -2514,7 +3200,7 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
2514
3200
  {
2515
3201
  title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
2516
3202
  task: async (ctx2) => {
2517
- const screenshotDir = path6.join(prdFolder, "screenshots");
3203
+ const screenshotDir = path9.join(prdFolder, "screenshots");
2518
3204
  const dirExists = await FileUtils.exists(screenshotDir);
2519
3205
  if (!dirExists) {
2520
3206
  logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
@@ -2525,7 +3211,7 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
2525
3211
  ctx2.screenshots = [];
2526
3212
  for (const ext of imageExtensions) {
2527
3213
  const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
2528
- ctx2.screenshots.push(...files.map((f) => path6.join(screenshotDir, f)));
3214
+ ctx2.screenshots.push(...files.map((f) => path9.join(screenshotDir, f)));
2529
3215
  }
2530
3216
  logger.success(`\u627E\u5230 ${ctx2.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
2531
3217
  }
@@ -2536,8 +3222,8 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
2536
3222
  const entries = await FileUtils.findFiles("*/", prdFolder);
2537
3223
  ctx2.demoRepos = [];
2538
3224
  for (const entry of entries) {
2539
- const dirPath = path6.join(prdFolder, entry);
2540
- const gitDir = path6.join(dirPath, ".git");
3225
+ const dirPath = path9.join(prdFolder, entry);
3226
+ const gitDir = path9.join(dirPath, ".git");
2541
3227
  const hasGit = await FileUtils.exists(gitDir);
2542
3228
  if (hasGit) {
2543
3229
  ctx2.demoRepos.push(dirPath);
@@ -2705,9 +3391,12 @@ IMPORTANT:
2705
3391
  }
2706
3392
 
2707
3393
  // src/commands/bugfix.ts
3394
+ init_esm_shims();
3395
+ init_utils();
3396
+ init_logger();
2708
3397
  import { Command as Command6 } from "commander";
2709
3398
  import inquirer5 from "inquirer";
2710
- import path7 from "path";
3399
+ import path10 from "path";
2711
3400
  import { Listr as Listr5 } from "listr2";
2712
3401
  var bugfixCommand = new Command6("bugfix").description("\u521B\u5EFA Bugfix \u8BB0\u5F55").action(async () => {
2713
3402
  try {
@@ -2756,7 +3445,7 @@ var bugfixCommand = new Command6("bugfix").description("\u521B\u5EFA Bugfix \u8B
2756
3445
  const relatedSpec = await findRelatedSpec(answers.description);
2757
3446
  const bugfixDir = "docs/bugfixes";
2758
3447
  await FileUtils.ensureDir(bugfixDir);
2759
- const bugfixFile = path7.join(bugfixDir, `${timestamp}_${bugId}.md`);
3448
+ const bugfixFile = path10.join(bugfixDir, `${timestamp}_${bugId}.md`);
2760
3449
  const content = formatBugfixDocument({
2761
3450
  id: bugId,
2762
3451
  severity: answers.severity,
@@ -2822,8 +3511,8 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
2822
3511
  {
2823
3512
  title: "\u521B\u5EFA hotfix \u5206\u652F",
2824
3513
  task: async () => {
2825
- const { execa: execa3 } = await import("execa");
2826
- await execa3("git", ["checkout", "-b", branchName], { stdio: "inherit" });
3514
+ const { execa: execa5 } = await import("execa");
3515
+ await execa5("git", ["checkout", "-b", branchName], { stdio: "inherit" });
2827
3516
  }
2828
3517
  },
2829
3518
  {
@@ -2832,7 +3521,7 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
2832
3521
  const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
2833
3522
  const hotfixDir = "docs/hotfixes";
2834
3523
  await FileUtils.ensureDir(hotfixDir);
2835
- const hotfixFile = path7.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
3524
+ const hotfixFile = path10.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
2836
3525
  const content = formatHotfixDocument({
2837
3526
  id: hotfixId,
2838
3527
  description: answers.description,
@@ -2847,9 +3536,9 @@ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D
2847
3536
  {
2848
3537
  title: "\u521B\u5EFA\u521D\u59CB commit",
2849
3538
  task: async () => {
2850
- const { execa: execa3 } = await import("execa");
2851
- await execa3("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
2852
- await execa3(
3539
+ const { execa: execa5 } = await import("execa");
3540
+ await execa5("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
3541
+ await execa5(
2853
3542
  "git",
2854
3543
  [
2855
3544
  "commit",
@@ -2907,7 +3596,7 @@ async function findRelatedSpec(description) {
2907
3596
  }
2908
3597
  const keywords = extractKeywords(description);
2909
3598
  for (const file of specs) {
2910
- const filePath = path7.join(specDir, file);
3599
+ const filePath = path10.join(specDir, file);
2911
3600
  const content = await FileUtils.read(filePath);
2912
3601
  for (const keyword of keywords) {
2913
3602
  if (content.toLowerCase().includes(keyword.toLowerCase())) {
@@ -2995,8 +3684,11 @@ git checkout ${data.branch}
2995
3684
  }
2996
3685
 
2997
3686
  // src/commands/lint.ts
3687
+ init_esm_shims();
3688
+ init_utils();
3689
+ init_logger();
2998
3690
  import { Command as Command7 } from "commander";
2999
- import { execa as execa2 } from "execa";
3691
+ import { execa as execa3 } from "execa";
3000
3692
  var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
3001
3693
  try {
3002
3694
  logger.header("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5");
@@ -3017,17 +3709,17 @@ var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D
3017
3709
  try {
3018
3710
  logger.step("\u68C0\u67E5\u524D\u7AEF\u4EE3\u7801...");
3019
3711
  if (options.fix) {
3020
- await execa2("npm", ["run", "lint", "--", "--fix"], {
3712
+ await execa3("npm", ["run", "lint", "--", "--fix"], {
3021
3713
  cwd: "frontend",
3022
3714
  stdio: "inherit"
3023
3715
  });
3024
3716
  } else {
3025
- await execa2("npm", ["run", "lint"], {
3717
+ await execa3("npm", ["run", "lint"], {
3026
3718
  cwd: "frontend",
3027
3719
  stdio: "inherit"
3028
3720
  });
3029
3721
  }
3030
- await execa2("npx", ["tsc", "--noEmit"], {
3722
+ await execa3("npx", ["tsc", "--noEmit"], {
3031
3723
  cwd: "frontend",
3032
3724
  stdio: "pipe"
3033
3725
  });
@@ -3050,23 +3742,23 @@ var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D
3050
3742
  logger.step("\u68C0\u67E5\u540E\u7AEF\u4EE3\u7801...");
3051
3743
  if (hasGradle) {
3052
3744
  if (options.fix) {
3053
- await execa2("./gradlew", ["spotlessApply"], {
3745
+ await execa3("./gradlew", ["spotlessApply"], {
3054
3746
  cwd: "backend",
3055
3747
  stdio: "inherit"
3056
3748
  });
3057
3749
  }
3058
- await execa2("./gradlew", ["checkstyleMain", "compileJava"], {
3750
+ await execa3("./gradlew", ["checkstyleMain", "compileJava"], {
3059
3751
  cwd: "backend",
3060
3752
  stdio: "inherit"
3061
3753
  });
3062
3754
  } else if (hasMaven) {
3063
3755
  if (options.fix) {
3064
- await execa2("./mvnw", ["spotless:apply"], {
3756
+ await execa3("./mvnw", ["spotless:apply"], {
3065
3757
  cwd: "backend",
3066
3758
  stdio: "inherit"
3067
3759
  });
3068
3760
  }
3069
- await execa2("./mvnw", ["checkstyle:check", "compile"], {
3761
+ await execa3("./mvnw", ["checkstyle:check", "compile"], {
3070
3762
  cwd: "backend",
3071
3763
  stdio: "inherit"
3072
3764
  });
@@ -3118,8 +3810,11 @@ var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D
3118
3810
  });
3119
3811
 
3120
3812
  // src/commands/status.ts
3813
+ init_esm_shims();
3814
+ init_utils();
3815
+ init_logger();
3121
3816
  import { Command as Command8 } from "commander";
3122
- import path8 from "path";
3817
+ import path11 from "path";
3123
3818
  var statusCommand = new Command8("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
3124
3819
  try {
3125
3820
  logger.header("\u9879\u76EE\u72B6\u6001");
@@ -3177,7 +3872,7 @@ async function displayFeatureInventory() {
3177
3872
  }
3178
3873
  const inventory = [];
3179
3874
  for (const file of specs) {
3180
- const filePath = path8.join(specDir, file);
3875
+ const filePath = path11.join(specDir, file);
3181
3876
  const content = await FileUtils.read(filePath);
3182
3877
  const status = parseSpecStatus2(content);
3183
3878
  inventory.push({
@@ -3236,7 +3931,7 @@ async function displayRecentActivity() {
3236
3931
  }
3237
3932
  const sorted = files.sort().reverse().slice(0, 5);
3238
3933
  for (const file of sorted) {
3239
- const filePath = path8.join(sessionDir, file);
3934
+ const filePath = path11.join(sessionDir, file);
3240
3935
  const stat = await FileUtils.read(filePath);
3241
3936
  const specMatch = stat.match(/\*\*Spec\*\*:\s*(.+)/);
3242
3937
  const spec = specMatch ? specMatch[1].trim() : "\u672A\u77E5";
@@ -3274,8 +3969,11 @@ function getProgress(spec) {
3274
3969
  }
3275
3970
 
3276
3971
  // src/commands/detect-deps.ts
3972
+ init_esm_shims();
3973
+ init_utils();
3974
+ init_logger();
3277
3975
  import { Command as Command9 } from "commander";
3278
- import path9 from "path";
3976
+ import path12 from "path";
3279
3977
  import inquirer6 from "inquirer";
3280
3978
  var detectDepsCommand = new Command9("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
3281
3979
  try {
@@ -3303,7 +4001,7 @@ var detectDepsCommand = new Command9("detect-deps").argument("[spec-file]", "Spe
3303
4001
  process.exit(1);
3304
4002
  }
3305
4003
  for (const spec of specs) {
3306
- const specPath = path9.join(specsDir, spec);
4004
+ const specPath = path12.join(specsDir, spec);
3307
4005
  logger.step(`\u5904\u7406: ${spec}`);
3308
4006
  await detectDependencies(specPath);
3309
4007
  logger.newLine();
@@ -3329,7 +4027,7 @@ async function detectDependencies(specFile) {
3329
4027
  logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
3330
4028
  const projectDir = ".";
3331
4029
  const allDeps = /* @__PURE__ */ new Set();
3332
- const backendDir = path9.join(projectDir, "backend");
4030
+ const backendDir = path12.join(projectDir, "backend");
3333
4031
  const backendExists = await FileUtils.exists(backendDir);
3334
4032
  if (backendExists) {
3335
4033
  const apiDeps = await scanBackendApiCalls(backendDir);
@@ -3339,7 +4037,7 @@ async function detectDependencies(specFile) {
3339
4037
  const serviceDeps = await scanBackendServiceRefs(backendDir);
3340
4038
  serviceDeps.forEach((d) => allDeps.add(d));
3341
4039
  }
3342
- const frontendDir = path9.join(projectDir, "frontend");
4040
+ const frontendDir = path12.join(projectDir, "frontend");
3343
4041
  const frontendExists = await FileUtils.exists(frontendDir);
3344
4042
  if (frontendExists) {
3345
4043
  const frontendDeps = await scanFrontendApiCalls(frontendDir);
@@ -3380,11 +4078,11 @@ async function detectDependencies(specFile) {
3380
4078
  }
3381
4079
  async function scanBackendApiCalls(backendDir) {
3382
4080
  const deps = [];
3383
- const srcDir = path9.join(backendDir, "src");
4081
+ const srcDir = path12.join(backendDir, "src");
3384
4082
  try {
3385
4083
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3386
4084
  for (const file of javaFiles) {
3387
- const filePath = path9.join(srcDir, file);
4085
+ const filePath = path12.join(srcDir, file);
3388
4086
  const content = await FileUtils.read(filePath);
3389
4087
  const pathRegex = /"(\/api\/[^"]+)"/g;
3390
4088
  let match;
@@ -3402,11 +4100,11 @@ async function scanBackendApiCalls(backendDir) {
3402
4100
  }
3403
4101
  async function scanBackendEntityRelations(backendDir) {
3404
4102
  const deps = [];
3405
- const srcDir = path9.join(backendDir, "src");
4103
+ const srcDir = path12.join(backendDir, "src");
3406
4104
  try {
3407
4105
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3408
4106
  for (const file of javaFiles) {
3409
- const filePath = path9.join(srcDir, file);
4107
+ const filePath = path12.join(srcDir, file);
3410
4108
  const content = await FileUtils.read(filePath);
3411
4109
  if (content.includes("@JoinColumn") || content.includes("@ManyToOne") || content.includes("@OneToMany")) {
3412
4110
  const typeRegex = /type\s*=\s*(\w+)/g;
@@ -3422,11 +4120,11 @@ async function scanBackendEntityRelations(backendDir) {
3422
4120
  }
3423
4121
  async function scanBackendServiceRefs(backendDir) {
3424
4122
  const deps = [];
3425
- const srcDir = path9.join(backendDir, "src");
4123
+ const srcDir = path12.join(backendDir, "src");
3426
4124
  try {
3427
4125
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3428
4126
  for (const file of javaFiles) {
3429
- const filePath = path9.join(srcDir, file);
4127
+ const filePath = path12.join(srcDir, file);
3430
4128
  const content = await FileUtils.read(filePath);
3431
4129
  const serviceRegex = /private\s+(\w+)Service/g;
3432
4130
  let match;
@@ -3442,11 +4140,11 @@ async function scanBackendServiceRefs(backendDir) {
3442
4140
  }
3443
4141
  async function scanFrontendApiCalls(frontendDir) {
3444
4142
  const deps = [];
3445
- const srcDir = path9.join(frontendDir, "src");
4143
+ const srcDir = path12.join(frontendDir, "src");
3446
4144
  try {
3447
4145
  const tsFiles = await FileUtils.findFiles("*.{ts,tsx,js,jsx}", srcDir);
3448
4146
  for (const file of tsFiles) {
3449
- const filePath = path9.join(srcDir, file);
4147
+ const filePath = path12.join(srcDir, file);
3450
4148
  const content = await FileUtils.read(filePath);
3451
4149
  const pathRegex = /"(\/api\/[^"]+)"/g;
3452
4150
  let match;
@@ -3532,8 +4230,11 @@ ${deps.map((d) => ` - [x] ${d}`).join("\n")}
3532
4230
  }
3533
4231
 
3534
4232
  // src/commands/sync-memory.ts
4233
+ init_esm_shims();
4234
+ init_utils();
4235
+ init_logger();
3535
4236
  import { Command as Command10 } from "commander";
3536
- import path10 from "path";
4237
+ import path13 from "path";
3537
4238
  var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
3538
4239
  try {
3539
4240
  logger.header("\u540C\u6B65 AI_MEMORY.md");
@@ -3553,6 +4254,7 @@ var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 A
3553
4254
  await syncFeatureInventory(aiMemoryFile, ".");
3554
4255
  await syncApiInventory(aiMemoryFile, ".");
3555
4256
  await syncDataModels(aiMemoryFile, ".");
4257
+ await syncTemplateVersions(aiMemoryFile, ".");
3556
4258
  await updateSyncTime(aiMemoryFile);
3557
4259
  logger.success("AI_MEMORY.md \u5DF2\u540C\u6B65");
3558
4260
  } catch (error) {
@@ -3565,7 +4267,7 @@ var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 A
3565
4267
  });
3566
4268
  async function syncFeatureInventory(aiMemoryFile, projectDir) {
3567
4269
  logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
3568
- const specsDir = path10.join(projectDir, "docs/specs");
4270
+ const specsDir = path13.join(projectDir, "docs/specs");
3569
4271
  const exists = await FileUtils.exists(specsDir);
3570
4272
  if (!exists) {
3571
4273
  return;
@@ -3580,7 +4282,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
3580
4282
  for (const specFile of specs) {
3581
4283
  const name = specFile.replace(".md", "");
3582
4284
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3583
- const specPath = path10.join(specsDir, specFile);
4285
+ const specPath = path13.join(specsDir, specFile);
3584
4286
  const content = await FileUtils.read(specPath);
3585
4287
  const status = parseSpecStatus3(content);
3586
4288
  const progress = getSpecProgress(content);
@@ -3598,7 +4300,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
3598
4300
  }
3599
4301
  async function syncApiInventory(aiMemoryFile, projectDir) {
3600
4302
  logger.step("\u540C\u6B65 API \u5217\u8868...");
3601
- const backendDir = path10.join(projectDir, "backend");
4303
+ const backendDir = path13.join(projectDir, "backend");
3602
4304
  const exists = await FileUtils.exists(backendDir);
3603
4305
  if (!exists) {
3604
4306
  return;
@@ -3608,13 +4310,13 @@ async function syncApiInventory(aiMemoryFile, projectDir) {
3608
4310
  lines.push("");
3609
4311
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210");
3610
4312
  lines.push("");
3611
- const srcDir = path10.join(backendDir, "src");
4313
+ const srcDir = path13.join(backendDir, "src");
3612
4314
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3613
4315
  if (controllers.length === 0) {
3614
4316
  lines.push("\u6682\u65E0 API");
3615
4317
  } else {
3616
4318
  for (const controllerFile of controllers) {
3617
- const controllerPath = path10.join(srcDir, controllerFile);
4319
+ const controllerPath = path13.join(srcDir, controllerFile);
3618
4320
  const controllerName = controllerFile.replace(".java", "");
3619
4321
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
3620
4322
  lines.push(`### ${module} \u6A21\u5757`);
@@ -3691,7 +4393,7 @@ function extractMethodComment(content, methodName) {
3691
4393
  }
3692
4394
  async function syncDataModels(aiMemoryFile, projectDir) {
3693
4395
  logger.step("\u540C\u6B65\u6570\u636E\u6A21\u578B...");
3694
- const backendDir = path10.join(projectDir, "backend");
4396
+ const backendDir = path13.join(projectDir, "backend");
3695
4397
  const exists = await FileUtils.exists(backendDir);
3696
4398
  if (!exists) {
3697
4399
  return;
@@ -3701,7 +4403,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
3701
4403
  lines.push("");
3702
4404
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Entity \u751F\u6210");
3703
4405
  lines.push("");
3704
- const srcDir = path10.join(backendDir, "src");
4406
+ const srcDir = path13.join(backendDir, "src");
3705
4407
  const entities = await FileUtils.findFiles("*Entity.java", srcDir);
3706
4408
  if (entities.length === 0) {
3707
4409
  lines.push("\u6682\u65E0\u6570\u636E\u6A21\u578B");
@@ -3709,7 +4411,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
3709
4411
  lines.push("| \u6A21\u578B | \u8BF4\u660E | \u5B57\u6BB5 | \u5173\u8054 |");
3710
4412
  lines.push("|------|------|------|------|");
3711
4413
  for (const entityFile of entities) {
3712
- const entityPath = path10.join(srcDir, entityFile);
4414
+ const entityPath = path13.join(srcDir, entityFile);
3713
4415
  const entityName = entityFile.replace(".java", "").replace(/Entity$/, "");
3714
4416
  const displayName = entityName.split(/(?=[A-Z])/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
3715
4417
  const content = await FileUtils.read(entityPath);
@@ -3798,10 +4500,56 @@ function getSpecProgress(spec) {
3798
4500
  }
3799
4501
  return `${completedTodos}/${totalTodos}`;
3800
4502
  }
4503
+ async function syncTemplateVersions(aiMemoryFile, projectDir) {
4504
+ logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
4505
+ const config = await readTemplateConfig(projectDir);
4506
+ if (!config) {
4507
+ return;
4508
+ }
4509
+ const lines = [];
4510
+ lines.push("## \u6A21\u677F\u7248\u672C\u4FE1\u606F");
4511
+ lines.push("");
4512
+ lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u7BA1\u7406\uFF0C\u8BB0\u5F55\u4F7F\u7528\u7684\u524D\u540E\u7AEF\u6A21\u677F\u7248\u672C");
4513
+ lines.push("");
4514
+ lines.push("| \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |");
4515
+ lines.push("|------|------|-----|--------|--------|----------|");
4516
+ if (config.frontend) {
4517
+ const repoName = extractRepoName(config.frontend.repository);
4518
+ const commit = config.frontend.commit?.substring(0, 8) || "-";
4519
+ const tag = config.frontend.tag || "-";
4520
+ const branch = config.frontend.branch || "-";
4521
+ const updateDate = config.frontend.lastUpdate ? new Date(config.frontend.lastUpdate).toLocaleDateString("zh-CN") : "-";
4522
+ lines.push(`| \u524D\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
4523
+ }
4524
+ if (config.backend) {
4525
+ const repoName = extractRepoName(config.backend.repository);
4526
+ const commit = config.backend.commit?.substring(0, 8) || "-";
4527
+ const tag = config.backend.tag || "-";
4528
+ const branch = config.backend.branch || "-";
4529
+ const updateDate = config.backend.lastUpdate ? new Date(config.backend.lastUpdate).toLocaleDateString("zh-CN") : "-";
4530
+ lines.push(`| \u540E\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
4531
+ }
4532
+ const newContent = lines.join("\n") + "\n";
4533
+ await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
4534
+ }
4535
+ function extractRepoName(repository) {
4536
+ let path17 = repository;
4537
+ path17 = path17.replace(/^https?:\/\//, "");
4538
+ path17 = path17.replace(/^git@/, "");
4539
+ const parts = path17.split("/");
4540
+ if (parts.length > 1) {
4541
+ path17 = parts.slice(1).join("/");
4542
+ }
4543
+ path17 = path17.replace(/\.git$/, "");
4544
+ return path17;
4545
+ }
3801
4546
 
3802
4547
  // src/commands/check-api.ts
4548
+ init_esm_shims();
4549
+ init_utils();
4550
+ init_logger();
3803
4551
  import { Command as Command11 } from "commander";
3804
- import path11 from "path";
4552
+ import path14 from "path";
3805
4553
  import inquirer7 from "inquirer";
3806
4554
  import { Listr as Listr6 } from "listr2";
3807
4555
  var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
@@ -3865,7 +4613,7 @@ var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\u
3865
4613
  }
3866
4614
  });
3867
4615
  async function checkApiConflicts(projectDir) {
3868
- const backendDir = path11.join(projectDir, "backend");
4616
+ const backendDir = path14.join(projectDir, "backend");
3869
4617
  const exists = await FileUtils.exists(backendDir);
3870
4618
  if (!exists) {
3871
4619
  logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE");
@@ -3874,10 +4622,10 @@ async function checkApiConflicts(projectDir) {
3874
4622
  logger.step("\u626B\u63CF\u540E\u7AEF API...");
3875
4623
  logger.newLine();
3876
4624
  const apiMap = /* @__PURE__ */ new Map();
3877
- const srcDir = path11.join(backendDir, "src");
4625
+ const srcDir = path14.join(backendDir, "src");
3878
4626
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3879
4627
  for (const controllerFile of controllers) {
3880
- const controllerPath = path11.join(srcDir, controllerFile);
4628
+ const controllerPath = path14.join(srcDir, controllerFile);
3881
4629
  const apis = await extractApisFromController(controllerPath);
3882
4630
  for (const api of apis) {
3883
4631
  const key = `${api.method}:${api.path}`;
@@ -3908,8 +4656,8 @@ async function checkApiConflicts(projectDir) {
3908
4656
  }
3909
4657
  }
3910
4658
  async function detectApiChanges(projectDir) {
3911
- const backendDir = path11.join(projectDir, "backend");
3912
- const registryFile = path11.join(projectDir, "docs/api-registry.md");
4659
+ const backendDir = path14.join(projectDir, "backend");
4660
+ const registryFile = path14.join(projectDir, "docs/api-registry.md");
3913
4661
  const registryExists = await FileUtils.exists(registryFile);
3914
4662
  if (!registryExists) {
3915
4663
  logger.info("API Registry \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u53D8\u66F4\u68C0\u6D4B");
@@ -3921,10 +4669,10 @@ async function detectApiChanges(projectDir) {
3921
4669
  const registryContent = await FileUtils.read(registryFile);
3922
4670
  const existingApis = extractApisFromRegistry(registryContent);
3923
4671
  const currentApis = /* @__PURE__ */ new Map();
3924
- const srcDir = path11.join(backendDir, "src");
4672
+ const srcDir = path14.join(backendDir, "src");
3925
4673
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3926
4674
  for (const controllerFile of controllers) {
3927
- const controllerPath = path11.join(srcDir, controllerFile);
4675
+ const controllerPath = path14.join(srcDir, controllerFile);
3928
4676
  const apis = await extractApisFromController(controllerPath);
3929
4677
  for (const api of apis) {
3930
4678
  const key = `${api.method}:${api.path}`;
@@ -3986,9 +4734,9 @@ async function detectApiChanges(projectDir) {
3986
4734
  }
3987
4735
  }
3988
4736
  async function generateApiRegistry(projectDir) {
3989
- const registryFile = path11.join(projectDir, "docs/api-registry.md");
4737
+ const registryFile = path14.join(projectDir, "docs/api-registry.md");
3990
4738
  logger.step("\u626B\u63CF\u5E76\u751F\u6210 API Registry...");
3991
- await FileUtils.ensureDir(path11.dirname(registryFile));
4739
+ await FileUtils.ensureDir(path14.dirname(registryFile));
3992
4740
  const header = `# API Registry
3993
4741
 
3994
4742
  > \u672C\u6587\u4EF6\u8BB0\u5F55\u6240\u6709 API \u7684\u5B9A\u4E49\u3001\u7248\u672C\u548C\u53D8\u66F4\u5386\u53F2
@@ -4017,14 +4765,14 @@ async function generateApiRegistry(projectDir) {
4017
4765
  *\u6700\u540E\u66F4\u65B0: ${DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss")}*
4018
4766
  `;
4019
4767
  let content = header;
4020
- const backendDir = path11.join(projectDir, "backend");
4768
+ const backendDir = path14.join(projectDir, "backend");
4021
4769
  const exists = await FileUtils.exists(backendDir);
4022
4770
  if (exists) {
4023
- const srcDir = path11.join(backendDir, "src");
4771
+ const srcDir = path14.join(backendDir, "src");
4024
4772
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
4025
4773
  const moduleMap = /* @__PURE__ */ new Map();
4026
4774
  for (const controllerFile of controllers) {
4027
- const controllerPath = path11.join(srcDir, controllerFile);
4775
+ const controllerPath = path14.join(srcDir, controllerFile);
4028
4776
  const controllerName = controllerFile.replace(".java", "");
4029
4777
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
4030
4778
  if (!moduleMap.has(module)) {
@@ -4108,10 +4856,10 @@ function extractApisFromRegistry(registryContent) {
4108
4856
  let match;
4109
4857
  while ((match = apiRegex.exec(registryContent)) !== null) {
4110
4858
  const method = match[1];
4111
- const path13 = match[2].trim();
4859
+ const path17 = match[2].trim();
4112
4860
  const description = match[3].trim();
4113
- const key = `${method}:${path13}`;
4114
- apis.set(key, { method, path: path13, description });
4861
+ const key = `${method}:${path17}`;
4862
+ apis.set(key, { method, path: path17, description });
4115
4863
  }
4116
4864
  return apis;
4117
4865
  }
@@ -4129,8 +4877,11 @@ function extractMethodComment2(content, methodName) {
4129
4877
  }
4130
4878
 
4131
4879
  // src/commands/logs.ts
4880
+ init_esm_shims();
4881
+ init_utils();
4882
+ init_logger();
4132
4883
  import { Command as Command12 } from "commander";
4133
- import path12 from "path";
4884
+ import path15 from "path";
4134
4885
  import inquirer8 from "inquirer";
4135
4886
  var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
4136
4887
  try {
@@ -4155,7 +4906,7 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
4155
4906
  case "":
4156
4907
  case "today": {
4157
4908
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4158
- targetDir = path12.join(sessionsDir, today);
4909
+ targetDir = path15.join(sessionsDir, today);
4159
4910
  const todayExists = await FileUtils.exists(targetDir);
4160
4911
  if (!todayExists) {
4161
4912
  logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
@@ -4171,7 +4922,7 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
4171
4922
  break;
4172
4923
  }
4173
4924
  default: {
4174
- targetDir = path12.join(sessionsDir, filter);
4925
+ targetDir = path15.join(sessionsDir, filter);
4175
4926
  const dateExists = await FileUtils.exists(targetDir);
4176
4927
  if (!dateExists) {
4177
4928
  logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
@@ -4195,7 +4946,7 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
4195
4946
  process.exit(0);
4196
4947
  }
4197
4948
  for (let i = 0; i < logs.length; i++) {
4198
- const relPath = path12.relative(sessionsDir, logs[i]);
4949
+ const relPath = path15.relative(sessionsDir, logs[i]);
4199
4950
  logger.step(`${i + 1}) ${relPath}`);
4200
4951
  }
4201
4952
  logger.newLine();
@@ -4236,7 +4987,7 @@ async function collectLogFiles(targetDir) {
4236
4987
  const allFiles = await FileUtils.findFiles("*.md", targetDir);
4237
4988
  const filtered = allFiles.filter((f) => f !== "index.md");
4238
4989
  for (const file of filtered) {
4239
- const filePath = path12.join(targetDir, file);
4990
+ const filePath = path15.join(targetDir, file);
4240
4991
  const stat = await FileUtils.exists(filePath);
4241
4992
  if (stat) {
4242
4993
  logs.push(filePath);
@@ -4247,8 +4998,682 @@ async function collectLogFiles(targetDir) {
4247
4998
  return logs;
4248
4999
  }
4249
5000
 
5001
+ // src/commands/update.ts
5002
+ init_esm_shims();
5003
+ import { Command as Command13 } from "commander";
5004
+ import path16 from "path";
5005
+ init_logger();
5006
+ init_utils();
5007
+ import { execa as execa4 } from "execa";
5008
+ import inquirer9 from "inquirer";
5009
+ import fs3 from "fs-extra";
5010
+ var updateCommand = new Command13("update").description("\u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C").option("-f, --frontend", "\u68C0\u67E5\u524D\u7AEF\u6A21\u677F\u66F4\u65B0").option("-b, --backend", "\u68C0\u67E5\u540E\u7AEF\u6A21\u677F\u66F4\u65B0").option("-a, --all", "\u68C0\u67E5\u6240\u6709\u6A21\u677F (\u9ED8\u8BA4)").option("-t, --tag <tag>", "\u66F4\u65B0\u5230\u6307\u5B9A\u6807\u7B7E").option("-B, --branch <branch>", "\u66F4\u65B0\u5230\u6307\u5B9A\u5206\u652F").option("--dry-run", "\u9884\u89C8\u66F4\u65B0\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
5011
+ try {
5012
+ logger.header("\u6A21\u677F\u7248\u672C\u68C0\u67E5");
5013
+ logger.newLine();
5014
+ const hasConfig = await FileUtils.exists("TECH_STACK.md");
5015
+ if (!hasConfig) {
5016
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
5017
+ logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
5018
+ process.exit(1);
5019
+ }
5020
+ const projectPath = ".";
5021
+ const checkAll = !options.frontend && !options.backend;
5022
+ const checkFrontend = options.frontend || checkAll;
5023
+ const checkBackend = options.backend || checkAll;
5024
+ const forceUpdate = options.tag || options.branch;
5025
+ const updates = [];
5026
+ const updateOptions = {
5027
+ tag: options.tag,
5028
+ branch: options.branch,
5029
+ dryRun: options.dryRun
5030
+ };
5031
+ if (checkFrontend) {
5032
+ logger.step("\u68C0\u67E5\u524D\u7AEF\u6A21\u677F...");
5033
+ const frontendInfo = await checkTemplateUpdate(projectPath, "frontend");
5034
+ if (frontendInfo) {
5035
+ if (frontendInfo.needsUpdate || forceUpdate) {
5036
+ const version = updateOptions.tag || updateOptions.branch || frontendInfo.latestTag || frontendInfo.latestCommit?.substring(0, 8);
5037
+ logger.success(`\u524D\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
5038
+ updates.push({ type: "frontend", info: frontendInfo, updateOptions });
5039
+ } else {
5040
+ logger.info("\u524D\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
5041
+ }
5042
+ } else {
5043
+ logger.info("\u524D\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
5044
+ }
5045
+ logger.newLine();
5046
+ }
5047
+ if (checkBackend) {
5048
+ logger.step("\u68C0\u67E5\u540E\u7AEF\u6A21\u677F...");
5049
+ const backendInfo = await checkTemplateUpdate(projectPath, "backend");
5050
+ if (backendInfo) {
5051
+ if (backendInfo.needsUpdate || forceUpdate) {
5052
+ const version = updateOptions.tag || updateOptions.branch || backendInfo.latestTag || backendInfo.latestCommit?.substring(0, 8);
5053
+ logger.success(`\u540E\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
5054
+ updates.push({ type: "backend", info: backendInfo, updateOptions });
5055
+ } else {
5056
+ logger.info("\u540E\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
5057
+ }
5058
+ } else {
5059
+ logger.info("\u540E\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
5060
+ }
5061
+ logger.newLine();
5062
+ }
5063
+ if (updates.length === 0) {
5064
+ logger.success("\u6240\u6709\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C!");
5065
+ return;
5066
+ }
5067
+ if (options.dryRun) {
5068
+ logger.header(`[Dry Run] \u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F`);
5069
+ } else {
5070
+ logger.header(`\u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F\u66F4\u65B0`);
5071
+ }
5072
+ logger.newLine();
5073
+ for (const update of updates) {
5074
+ const version = update.updateOptions?.tag || update.updateOptions?.branch || update.info.latestTag || update.info.latestCommit?.substring(0, 8);
5075
+ logger.step(`${update.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${version}`);
5076
+ }
5077
+ logger.newLine();
5078
+ if (options.dryRun) {
5079
+ logger.info("Dry run \u6A21\u5F0F\uFF0C\u4E0D\u6267\u884C\u5B9E\u9645\u66F4\u65B0");
5080
+ return;
5081
+ }
5082
+ const answers = await inquirer9.prompt([
5083
+ {
5084
+ type: "confirm",
5085
+ name: "shouldUpdate",
5086
+ message: "\u662F\u5426\u66F4\u65B0\u6A21\u677F?",
5087
+ default: false
5088
+ }
5089
+ ]);
5090
+ if (answers.shouldUpdate) {
5091
+ await performUpdate(projectPath, updates);
5092
+ } else {
5093
+ logger.info("\u5DF2\u53D6\u6D88\u66F4\u65B0");
5094
+ }
5095
+ } catch (error) {
5096
+ logger.error(`\u66F4\u65B0\u68C0\u67E5\u5931\u8D25: ${error.message}`);
5097
+ if (process.env.DEBUG) {
5098
+ console.error(error);
5099
+ }
5100
+ process.exit(1);
5101
+ }
5102
+ });
5103
+ async function performUpdate(projectPath, updates) {
5104
+ logger.newLine();
5105
+ logger.info("\u5F00\u59CB\u66F4\u65B0\u6A21\u677F...");
5106
+ for (const update of updates) {
5107
+ const { type, info, updateOptions } = update;
5108
+ const targetDir = type === "frontend" ? "frontend" : "backend";
5109
+ const targetPath = path16.join(projectPath, targetDir);
5110
+ logger.newLine();
5111
+ logger.step(`\u66F4\u65B0 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
5112
+ if (updateOptions?.tag || updateOptions?.branch) {
5113
+ const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
5114
+ const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
5115
+ const config = await userConfigManager2.getGitLabConfig();
5116
+ if (config) {
5117
+ const gitlabAPI = new GitLabAPI2(config);
5118
+ const projectPathEncoded = GitLabAPI2.parseProjectPath(info.repository);
5119
+ if (updateOptions.tag) {
5120
+ const isValid = await gitlabAPI.validateTag(projectPathEncoded, updateOptions.tag);
5121
+ if (!isValid) {
5122
+ logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag "${updateOptions.tag}" \u4E0D\u5B58\u5728`);
5123
+ continue;
5124
+ }
5125
+ logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag: ${updateOptions.tag}`);
5126
+ }
5127
+ if (updateOptions.branch) {
5128
+ const isValid = await gitlabAPI.validateBranch(projectPathEncoded, updateOptions.branch);
5129
+ if (!isValid) {
5130
+ logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F "${updateOptions.branch}" \u4E0D\u5B58\u5728`);
5131
+ continue;
5132
+ }
5133
+ logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F: ${updateOptions.branch}`);
5134
+ }
5135
+ } else {
5136
+ logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
5137
+ }
5138
+ }
5139
+ const ref = updateOptions?.tag || updateOptions?.branch || "HEAD";
5140
+ const backupDir = path16.join(projectPath, `.backup-${Date.now()}`);
5141
+ await fs3.copy(targetPath, path16.join(backupDir, targetDir));
5142
+ logger.info(`\u5DF2\u521B\u5EFA\u5907\u4EFD: ${backupDir}`);
5143
+ if (updateOptions?.dryRun) {
5144
+ logger.info("[Dry Run] \u5C06\u4F1A\u66F4\u65B0\u5230\u4EE5\u4E0B\u7248\u672C:");
5145
+ logger.info(` Ref: ${ref}`);
5146
+ logger.info(` \u4ED3\u5E93: ${info.repository}`);
5147
+ logger.info("\u5907\u4EFD\u5DF2\u521B\u5EFA\uFF0C\u8DF3\u8FC7\u5B9E\u9645\u66F4\u65B0");
5148
+ continue;
5149
+ }
5150
+ try {
5151
+ const tempDir = path16.join(projectPath, `.template-update-${Date.now()}`);
5152
+ await execa4("git", ["clone", "--depth=1", "--branch", ref, info.repository, tempDir], {
5153
+ stdio: "pipe"
5154
+ });
5155
+ const { stdout: commit } = await execa4("git", ["rev-parse", "HEAD"], {
5156
+ cwd: tempDir,
5157
+ stdio: "pipe"
5158
+ });
5159
+ const { stdout: tags } = await execa4("git", ["tag", "-l", "--sort=-v:refname"], {
5160
+ cwd: tempDir,
5161
+ stdio: "pipe"
5162
+ });
5163
+ const latestTag = tags.split("\n")[0] || void 0;
5164
+ const keepFiles = [".env.local", "package-lock.json", "node_modules"];
5165
+ const currentFiles = await FileUtils.findFiles("*", targetPath);
5166
+ for (const file of currentFiles) {
5167
+ if (!keepFiles.includes(file)) {
5168
+ const filePath = path16.join(targetPath, file);
5169
+ try {
5170
+ await fs3.remove(filePath);
5171
+ } catch {
5172
+ }
5173
+ }
5174
+ }
5175
+ await fs3.copy(tempDir, targetPath, {
5176
+ filter: (src) => !src.includes(".git")
5177
+ });
5178
+ await fs3.remove(tempDir);
5179
+ await updateTemplateVersion(projectPath, type, commit.trim(), {
5180
+ tag: updateOptions?.tag || latestTag,
5181
+ branch: updateOptions?.branch
5182
+ });
5183
+ logger.success(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!`);
5184
+ logger.info(`\u65B0\u7248\u672C: ${updateOptions?.tag || updateOptions?.branch || latestTag || commit.substring(0, 8)}`);
5185
+ logger.info(`\u5907\u4EFD\u4F4D\u7F6E: ${backupDir}`);
5186
+ } catch (error) {
5187
+ logger.error(`\u66F4\u65B0\u5931\u8D25: ${error.message}`);
5188
+ logger.info("\u6B63\u5728\u6062\u590D\u5907\u4EFD...");
5189
+ await fs3.remove(targetPath);
5190
+ await fs3.copy(path16.join(backupDir, targetDir), targetPath);
5191
+ await fs3.remove(backupDir);
5192
+ logger.info("\u5DF2\u6062\u590D\u5230\u66F4\u65B0\u524D\u7684\u72B6\u6001");
5193
+ }
5194
+ }
5195
+ logger.newLine();
5196
+ logger.header("\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!");
5197
+ logger.newLine();
5198
+ logger.info("\u4E0B\u4E00\u6B65:");
5199
+ logger.step("1. \u68C0\u67E5\u66F4\u65B0\u540E\u7684\u4EE3\u7801");
5200
+ logger.step("2. \u8FD0\u884C npm install \u5B89\u88C5\u65B0\u4F9D\u8D56");
5201
+ logger.step("3. \u6D4B\u8BD5\u5E94\u7528\u662F\u5426\u6B63\u5E38\u8FD0\u884C");
5202
+ logger.step("4. \u63D0\u4EA4\u4EE3\u7801\u5230 Git");
5203
+ logger.newLine();
5204
+ }
5205
+
5206
+ // src/commands/config.ts
5207
+ init_esm_shims();
5208
+ init_user_config();
5209
+ init_gitlab_api();
5210
+ init_logger();
5211
+ import { Command as Command14 } from "commander";
5212
+ import inquirer10 from "inquirer";
5213
+ import chalk2 from "chalk";
5214
+ var configCommand = new Command14("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
5215
+ var setTokenCommand = new Command14("set-token").description("\u8BBE\u7F6E GitLab Access Token").option("-t, --token <token>", "Access Token").option("-u, --url <url>", "GitLab Base URL", "https://gitlab.com").action(async (options) => {
5216
+ try {
5217
+ logger.header("GitLab Access Token \u914D\u7F6E");
5218
+ logger.newLine();
5219
+ let { token, url } = options;
5220
+ if (!token) {
5221
+ const answers = await inquirer10.prompt([
5222
+ {
5223
+ type: "password",
5224
+ name: "token",
5225
+ message: "\u8BF7\u8F93\u5165 GitLab Access Token:",
5226
+ mask: "*",
5227
+ validate: (input) => {
5228
+ if (!input || input.trim().length === 0) {
5229
+ return "Token \u4E0D\u80FD\u4E3A\u7A7A";
5230
+ }
5231
+ return true;
5232
+ }
5233
+ },
5234
+ {
5235
+ type: "input",
5236
+ name: "url",
5237
+ message: "\u8BF7\u8F93\u5165 GitLab Base URL:",
5238
+ default: "https://gitlab.com",
5239
+ validate: (input) => {
5240
+ try {
5241
+ new URL(input);
5242
+ return true;
5243
+ } catch {
5244
+ return "\u8BF7\u8F93\u5165\u6709\u6548\u7684 URL";
5245
+ }
5246
+ }
5247
+ }
5248
+ ]);
5249
+ token = answers.token;
5250
+ url = answers.url;
5251
+ }
5252
+ try {
5253
+ new URL(url);
5254
+ } catch {
5255
+ logger.error("\u65E0\u6548\u7684 GitLab URL");
5256
+ process.exit(1);
5257
+ }
5258
+ url = url.replace(/\/+$/, "");
5259
+ logger.info("\u9A8C\u8BC1 Token...");
5260
+ const gitlabAPI = new GitLabAPI({ accessToken: token, baseUrl: url });
5261
+ const isValid = await gitlabAPI.authenticate();
5262
+ if (!isValid) {
5263
+ logger.error("Token \u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5:");
5264
+ logger.info("1. Token \u662F\u5426\u6B63\u786E");
5265
+ logger.info("2. Token \u662F\u5426\u6709 api \u6743\u9650");
5266
+ logger.info("3. GitLab URL \u662F\u5426\u6B63\u786E");
5267
+ process.exit(1);
5268
+ }
5269
+ logger.success("Token \u9A8C\u8BC1\u6210\u529F!");
5270
+ await userConfigManager.updateGitLabToken(token, url);
5271
+ logger.success("\u914D\u7F6E\u5DF2\u4FDD\u5B58!");
5272
+ logger.newLine();
5273
+ logger.info(`GitLab URL: ${chalk2.cyan(url)}`);
5274
+ logger.info(`\u914D\u7F6E\u6587\u4EF6: ${chalk2.gray(userConfigManager.getConfigPath())}`);
5275
+ } catch (error) {
5276
+ logger.error(`\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5277
+ if (process.env.DEBUG) {
5278
+ console.error(error);
5279
+ }
5280
+ process.exit(1);
5281
+ }
5282
+ });
5283
+ var showConfigCommand = new Command14("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
5284
+ try {
5285
+ logger.header("GitLab \u914D\u7F6E");
5286
+ logger.newLine();
5287
+ const hasConfig = await userConfigManager.hasConfig();
5288
+ if (!hasConfig) {
5289
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5290
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5291
+ } else {
5292
+ const config = await userConfigManager.load();
5293
+ if (config?.gitlab) {
5294
+ const token = config.gitlab.accessToken;
5295
+ const maskedToken = token ? `${token.substring(0, 8)}${"*".repeat(Math.min(16, token.length - 8))}` : "";
5296
+ logger.info(`GitLab URL: ${chalk2.cyan(config.gitlab.baseUrl)}`);
5297
+ logger.info(`Access Token: ${chalk2.yellow(maskedToken)}`);
5298
+ logger.info(`Timeout: ${chalk2.gray(config.gitlab.timeout || 3e4)}ms`);
5299
+ if (config.preferences) {
5300
+ logger.newLine();
5301
+ logger.info("\u504F\u597D\u8BBE\u7F6E:");
5302
+ if (config.preferences.defaultBackendBranch) {
5303
+ logger.info(` \u9ED8\u8BA4\u540E\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultBackendBranch)}`);
5304
+ }
5305
+ if (config.preferences.defaultFrontendBranch) {
5306
+ logger.info(` \u9ED8\u8BA4\u524D\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultFrontendBranch)}`);
5307
+ }
5308
+ }
5309
+ logger.newLine();
5310
+ logger.info("\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E:");
5311
+ logger.info(` ${chalk2.gray(userConfigManager.getConfigPath())}`);
5312
+ }
5313
+ }
5314
+ } catch (error) {
5315
+ logger.error(`\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5316
+ if (process.env.DEBUG) {
5317
+ console.error(error);
5318
+ }
5319
+ process.exit(1);
5320
+ }
5321
+ });
5322
+ var removeConfigCommand = new Command14("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
5323
+ try {
5324
+ const hasConfig = await userConfigManager.hasConfig();
5325
+ if (!hasConfig) {
5326
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5327
+ return;
5328
+ }
5329
+ const answers = await inquirer10.prompt([
5330
+ {
5331
+ type: "confirm",
5332
+ name: "confirm",
5333
+ message: "\u786E\u5B9A\u8981\u5220\u9664 GitLab \u914D\u7F6E\u5417?",
5334
+ default: false
5335
+ }
5336
+ ]);
5337
+ if (!answers.confirm) {
5338
+ logger.info("\u5DF2\u53D6\u6D88");
5339
+ return;
5340
+ }
5341
+ await userConfigManager.removeConfig();
5342
+ logger.success("\u914D\u7F6E\u5DF2\u5220\u9664");
5343
+ } catch (error) {
5344
+ logger.error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5345
+ if (process.env.DEBUG) {
5346
+ console.error(error);
5347
+ }
5348
+ process.exit(1);
5349
+ }
5350
+ });
5351
+ var validateTokenCommand = new Command14("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
5352
+ try {
5353
+ logger.header("\u9A8C\u8BC1 GitLab Token");
5354
+ logger.newLine();
5355
+ const config = await userConfigManager.getGitLabConfig();
5356
+ if (!config) {
5357
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5358
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5359
+ process.exit(1);
5360
+ }
5361
+ logger.info("\u6B63\u5728\u9A8C\u8BC1...");
5362
+ const gitlabAPI = new GitLabAPI(config);
5363
+ const isValid = await gitlabAPI.authenticate();
5364
+ if (isValid) {
5365
+ logger.success("Token \u6709\u6548!");
5366
+ logger.newLine();
5367
+ logger.info(`GitLab URL: ${chalk2.cyan(config.baseUrl)}`);
5368
+ } else {
5369
+ logger.error("Token \u9A8C\u8BC1\u5931\u8D25");
5370
+ logger.info("\u8BF7\u68C0\u67E5 Token \u662F\u5426\u6B63\u786E\u6216\u5DF2\u8FC7\u671F");
5371
+ process.exit(1);
5372
+ }
5373
+ } catch (error) {
5374
+ logger.error(`\u9A8C\u8BC1\u5931\u8D25: ${error.message}`);
5375
+ if (process.env.DEBUG) {
5376
+ console.error(error);
5377
+ }
5378
+ process.exit(1);
5379
+ }
5380
+ });
5381
+
5382
+ // src/commands/diff.ts
5383
+ init_esm_shims();
5384
+ import { Command as Command15 } from "commander";
5385
+ import chalk3 from "chalk";
5386
+ init_logger();
5387
+ init_utils();
5388
+ init_user_config();
5389
+ init_gitlab_api();
5390
+ var diffCommand = new Command15("diff").description("\u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02").option("-f, --frontend", "\u5BF9\u6BD4\u524D\u7AEF\u6A21\u677F").option("-b, --backend", "\u5BF9\u6BD4\u540E\u7AEF\u6A21\u677F").option("-t, --tag <tag>", "\u6307\u5B9A\u8FDC\u7A0B\u6807\u7B7E").option("-B, --branch <branch>", "\u6307\u5B9A\u8FDC\u7A0B\u5206\u652F").option("-o, --output <format>", "\u8F93\u51FA\u683C\u5F0F (table|json|diff)", "table").action(async (options) => {
5391
+ try {
5392
+ logger.header("\u6A21\u677F\u7248\u672C\u5BF9\u6BD4");
5393
+ logger.newLine();
5394
+ const hasConfig = await FileUtils.exists("TECH_STACK.md");
5395
+ if (!hasConfig) {
5396
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
5397
+ logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
5398
+ process.exit(1);
5399
+ }
5400
+ const projectPath = ".";
5401
+ const checkAll = !options.frontend && !options.backend;
5402
+ const checkFrontend = options.frontend || checkAll;
5403
+ const checkBackend = options.backend || checkAll;
5404
+ const config = await readTemplateConfig(projectPath);
5405
+ if (!config) {
5406
+ logger.error("\u672A\u627E\u5230\u6A21\u677F\u914D\u7F6E");
5407
+ process.exit(1);
5408
+ }
5409
+ const gitlabConfig = await userConfigManager.getGitLabConfig();
5410
+ if (!gitlabConfig) {
5411
+ logger.warn("\u672A\u914D\u7F6E GitLab Token");
5412
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5413
+ logger.info("\u5C06\u4F7F\u7528\u57FA\u672C git \u547D\u4EE4\u8FDB\u884C\u5BF9\u6BD4");
5414
+ }
5415
+ const results = [];
5416
+ if (checkFrontend && config.frontend) {
5417
+ const result = await compareTemplate(
5418
+ projectPath,
5419
+ "frontend",
5420
+ config.frontend,
5421
+ options.tag,
5422
+ options.branch,
5423
+ gitlabConfig || void 0
5424
+ );
5425
+ if (result) {
5426
+ results.push(result);
5427
+ }
5428
+ }
5429
+ if (checkBackend && config.backend) {
5430
+ const result = await compareTemplate(
5431
+ projectPath,
5432
+ "backend",
5433
+ config.backend,
5434
+ options.tag,
5435
+ options.branch,
5436
+ gitlabConfig || void 0
5437
+ );
5438
+ if (result) {
5439
+ results.push(result);
5440
+ }
5441
+ }
5442
+ if (results.length === 0) {
5443
+ logger.info("\u6CA1\u6709\u53EF\u5BF9\u6BD4\u7684\u6A21\u677F");
5444
+ return;
5445
+ }
5446
+ switch (options.output) {
5447
+ case "json":
5448
+ outputJson(results);
5449
+ break;
5450
+ case "diff":
5451
+ outputDiff(results);
5452
+ break;
5453
+ case "table":
5454
+ default:
5455
+ outputTable(results);
5456
+ break;
5457
+ }
5458
+ } catch (error) {
5459
+ logger.error(`\u5BF9\u6BD4\u5931\u8D25: ${error.message}`);
5460
+ if (process.env.DEBUG) {
5461
+ console.error(error);
5462
+ }
5463
+ process.exit(1);
5464
+ }
5465
+ });
5466
+ async function compareTemplate(projectPath, type, localConfig, remoteTag, remoteBranch, gitlabConfig) {
5467
+ try {
5468
+ logger.step(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
5469
+ const repository = localConfig.repository;
5470
+ const localCommit = localConfig.commit;
5471
+ const localTag = localConfig.tag;
5472
+ const localBranch = localConfig.branch;
5473
+ let remoteCommit = null;
5474
+ let remoteTagName;
5475
+ let remoteBranchName;
5476
+ if (gitlabConfig) {
5477
+ const gitlabAPI = new GitLabAPI(gitlabConfig);
5478
+ const projectPathEncoded = GitLabAPI.parseProjectPath(repository);
5479
+ if (remoteTag) {
5480
+ remoteCommit = await gitlabAPI.getTagCommit(projectPathEncoded, remoteTag);
5481
+ remoteTagName = remoteTag;
5482
+ } else if (remoteBranch) {
5483
+ remoteCommit = await gitlabAPI.getBranchCommit(projectPathEncoded, remoteBranch);
5484
+ remoteBranchName = remoteBranch;
5485
+ } else {
5486
+ const latest = await gitlabAPI.getLatestVersion(projectPathEncoded);
5487
+ if (latest) {
5488
+ remoteCommit = latest.commit;
5489
+ if (latest.type === "tag") {
5490
+ remoteTagName = latest.name;
5491
+ } else {
5492
+ remoteBranchName = latest.name;
5493
+ }
5494
+ }
5495
+ }
5496
+ if (remoteCommit && localCommit && remoteCommit !== localCommit) {
5497
+ const from = localTag || localBranch || localCommit;
5498
+ const to = remoteTagName || remoteBranchName || remoteCommit;
5499
+ const diff = await gitlabAPI.compareVersions(projectPathEncoded, from, to);
5500
+ return {
5501
+ type,
5502
+ local: {
5503
+ commit: localCommit,
5504
+ tag: localTag,
5505
+ branch: localBranch
5506
+ },
5507
+ remote: {
5508
+ commit: remoteCommit,
5509
+ tag: remoteTagName,
5510
+ branch: remoteBranchName
5511
+ },
5512
+ diff: diff || void 0
5513
+ };
5514
+ }
5515
+ }
5516
+ if (!remoteCommit) {
5517
+ const { execa: execa5 } = await import("execa");
5518
+ const ref = remoteTag || remoteBranch || "HEAD";
5519
+ const { stdout } = await execa5("git", ["ls-remote", repository, ref], {
5520
+ stdio: "pipe"
5521
+ });
5522
+ remoteCommit = stdout.split(" ")[0];
5523
+ remoteTagName = remoteTag;
5524
+ remoteBranchName = remoteBranch;
5525
+ }
5526
+ const needsUpdate = localCommit !== remoteCommit;
5527
+ logger.info(
5528
+ `${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${needsUpdate ? chalk3.yellow("\u6709\u66F4\u65B0") : chalk3.green("\u5DF2\u662F\u6700\u65B0")}`
5529
+ );
5530
+ return {
5531
+ type,
5532
+ local: {
5533
+ commit: localCommit,
5534
+ tag: localTag,
5535
+ branch: localBranch
5536
+ },
5537
+ remote: {
5538
+ commit: remoteCommit || void 0,
5539
+ tag: remoteTagName,
5540
+ branch: remoteBranchName
5541
+ }
5542
+ };
5543
+ } catch (error) {
5544
+ logger.error(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5931\u8D25: ${error}`);
5545
+ return null;
5546
+ }
5547
+ }
5548
+ function outputTable(results) {
5549
+ logger.newLine();
5550
+ for (const result of results) {
5551
+ const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
5552
+ console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
5553
+ console.log("");
5554
+ const t = new Table({
5555
+ head: [chalk3.cyan("\u9879\u76EE"), chalk3.cyan("\u7248\u672C")],
5556
+ colWidths: [20, 50]
5557
+ });
5558
+ const localVersion = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "-";
5559
+ const localInfo = `Commit: ${result.local.commit?.substring(0, 8) || "-"}${result.local.tag ? `
5560
+ Tag: ${result.local.tag}` : ""}${result.local.branch ? `
5561
+ Branch: ${result.local.branch}` : ""}`;
5562
+ t.push(["\u672C\u5730", localInfo]);
5563
+ const remoteVersion = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "-";
5564
+ const remoteInfo = `Commit: ${result.remote.commit?.substring(0, 8) || "-"}${result.remote.tag ? `
5565
+ Tag: ${result.remote.tag}` : ""}${result.remote.branch ? `
5566
+ Branch: ${result.remote.branch}` : ""}`;
5567
+ t.push(["\u8FDC\u7A0B", remoteInfo]);
5568
+ console.log(t.toString());
5569
+ if (result.diff && result.diff.commits.length > 0) {
5570
+ console.log("");
5571
+ console.log(chalk3.bold("\u65B0\u589E\u63D0\u4EA4:"));
5572
+ const commitsTable = new Table({
5573
+ head: [chalk3.cyan("Commit"), chalk3.cyan("\u4F5C\u8005"), chalk3.cyan("\u65F6\u95F4"), chalk3.cyan("\u63CF\u8FF0")],
5574
+ colWidths: [10, 15, 20, 50],
5575
+ wordWrap: true
5576
+ });
5577
+ for (const commit of result.diff.commits.slice(0, 10)) {
5578
+ commitsTable.push([
5579
+ commit.short_id,
5580
+ commit.author_name,
5581
+ new Date(commit.created_at).toLocaleDateString("zh-CN"),
5582
+ commit.title
5583
+ ]);
5584
+ }
5585
+ console.log(commitsTable.toString());
5586
+ if (result.diff.commits.length > 10) {
5587
+ console.log(chalk3.gray(`... \u8FD8\u6709 ${result.diff.commits.length - 10} \u4E2A\u63D0\u4EA4`));
5588
+ }
5589
+ const files = result.diff.diffs;
5590
+ const added = files.filter((f) => f.new_file).length;
5591
+ const deleted = files.filter((f) => f.deleted_file).length;
5592
+ const modified = files.length - added - deleted;
5593
+ console.log("");
5594
+ console.log(chalk3.bold("\u6587\u4EF6\u53D8\u66F4:"));
5595
+ console.log(` ${chalk3.green("+")} \u65B0\u589E: ${added}`);
5596
+ console.log(` ${chalk3.red("-")} \u5220\u9664: ${deleted}`);
5597
+ console.log(` ${chalk3.yellow("~")} \u4FEE\u6539: ${modified}`);
5598
+ }
5599
+ console.log("");
5600
+ }
5601
+ }
5602
+ function outputJson(results) {
5603
+ console.log(JSON.stringify(results, null, 2));
5604
+ }
5605
+ function outputDiff(results) {
5606
+ logger.newLine();
5607
+ for (const result of results) {
5608
+ const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
5609
+ console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
5610
+ console.log("");
5611
+ const from = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "unknown";
5612
+ const to = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "unknown";
5613
+ console.log(`\u672C\u5730\u7248\u672C: ${chalk3.cyan(from)}`);
5614
+ console.log(`\u8FDC\u7A0B\u7248\u672C: ${chalk3.cyan(to)}`);
5615
+ if (result.diff && result.diff.commits.length > 0) {
5616
+ console.log("");
5617
+ console.log(chalk3.bold("\u63D0\u4EA4\u5386\u53F2:"));
5618
+ for (const commit of result.diff.commits) {
5619
+ console.log("");
5620
+ console.log(chalk3.yellow(`commit ${commit.id}`));
5621
+ console.log(`Author: ${commit.author_name} <${commit.author_email}>`);
5622
+ console.log(`Date: ${new Date(commit.created_at).toLocaleString("zh-CN")}`);
5623
+ console.log("");
5624
+ console.log(` ${commit.title}`);
5625
+ if (commit.message) {
5626
+ console.log(` ${commit.message.split("\n").join("\n ")}`);
5627
+ }
5628
+ }
5629
+ }
5630
+ console.log("");
5631
+ }
5632
+ }
5633
+ var Table = class {
5634
+ options;
5635
+ rows = [];
5636
+ constructor(options) {
5637
+ this.options = options;
5638
+ }
5639
+ push(row) {
5640
+ this.rows.push(row);
5641
+ }
5642
+ toString() {
5643
+ if (this.rows.length === 0) return "";
5644
+ let result = "";
5645
+ const colWidths = this.options.colWidths || [];
5646
+ if (this.options.head) {
5647
+ result += "| ";
5648
+ for (let i = 0; i < this.options.head.length; i++) {
5649
+ const width = colWidths[i] || 20;
5650
+ const cell = this.options.head[i] || "";
5651
+ result += cell.padEnd(width) + " | ";
5652
+ }
5653
+ result += "\n";
5654
+ result += "|";
5655
+ for (let i = 0; i < this.options.head.length; i++) {
5656
+ const width = colWidths[i] || 20;
5657
+ result += "-".repeat(width + 2) + "|";
5658
+ }
5659
+ result += "\n";
5660
+ }
5661
+ for (const row of this.rows) {
5662
+ result += "| ";
5663
+ for (let i = 0; i < row.length; i++) {
5664
+ const width = colWidths[i] || 20;
5665
+ const cell = String(row[i] || "");
5666
+ const displayCell = this.options.wordWrap && cell.length > width ? cell.substring(0, width - 3) + "..." : cell;
5667
+ result += displayCell.padEnd(width) + " | ";
5668
+ }
5669
+ result += "\n";
5670
+ }
5671
+ return result;
5672
+ }
5673
+ };
5674
+
4250
5675
  // src/index.ts
4251
- var program = new Command13();
5676
+ var program = new Command16();
4252
5677
  program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version("2.0.0");
4253
5678
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
4254
5679
  program.addCommand(initCommand);
@@ -4264,6 +5689,9 @@ program.addCommand(detectDepsCommand);
4264
5689
  program.addCommand(syncMemoryCommand);
4265
5690
  program.addCommand(checkApiCommand);
4266
5691
  program.addCommand(logsCommand);
5692
+ program.addCommand(updateCommand);
5693
+ program.addCommand(configCommand);
5694
+ program.addCommand(diffCommand);
4267
5695
  program.action(() => {
4268
5696
  showHelp();
4269
5697
  });
@@ -4271,7 +5699,7 @@ function showHelp() {
4271
5699
  console.log("");
4272
5700
  logger.header("team-cli - AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6");
4273
5701
  console.log("");
4274
- console.log(chalk2.bold("\u4F7F\u7528\u65B9\u6CD5:"));
5702
+ console.log(chalk4.bold("\u4F7F\u7528\u65B9\u6CD5:"));
4275
5703
  console.log(" team-cli init [project-name] \u521D\u59CB\u5316\u65B0\u9879\u76EE");
4276
5704
  console.log(" team-cli split-prd <prd-folder> \u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs");
4277
5705
  console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
@@ -4285,21 +5713,24 @@ function showHelp() {
4285
5713
  console.log(" team-cli status \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4286
5714
  console.log(" team-cli lint \u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF+\u540E\u7AEF)");
4287
5715
  console.log(" team-cli logs [date] \u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7");
5716
+ console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
5717
+ console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
5718
+ console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
4288
5719
  console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
4289
5720
  console.log("");
4290
- console.log(chalk2.bold("\u793A\u4F8B:"));
5721
+ console.log(chalk4.bold("\u793A\u4F8B:"));
4291
5722
  console.log(" team-cli init my-project");
4292
5723
  console.log(" cd my-project");
4293
5724
  console.log(" team-cli add-feature payment-system");
4294
5725
  console.log(" team-cli breakdown docs/specs/xxx.md");
4295
5726
  console.log(" team-cli dev");
4296
5727
  console.log("");
4297
- console.log(chalk2.bold("\u5F00\u53D1\u6D41\u7A0B:"));
5728
+ console.log(chalk4.bold("\u5F00\u53D1\u6D41\u7A0B:"));
4298
5729
  console.log(" 1. PRD \u2192 specs (split-prd)");
4299
5730
  console.log(" 2. spec \u2192 milestones + todos (breakdown)");
4300
5731
  console.log(" 3. \u9009\u62E9 milestone/todo \u2192 \u5B9E\u73B0 (dev)");
4301
5732
  console.log("");
4302
- console.log(chalk2.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
5733
+ console.log(chalk4.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
4303
5734
  console.log(" team-cli add-feature <name> # \u6DFB\u52A0\u65B0\u529F\u80FD");
4304
5735
  console.log(" team-cli detect-deps [spec] # \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
4305
5736
  console.log(" team-cli sync-memory # \u540C\u6B65 AI_MEMORY");
@@ -4308,7 +5739,14 @@ function showHelp() {
4308
5739
  console.log(" team-cli hotfix # \u7D27\u6025\u4FEE\u590D");
4309
5740
  console.log(" team-cli status # \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4310
5741
  console.log("");
4311
- console.log(chalk2.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
5742
+ console.log(chalk4.bold("\u6A21\u677F\u7BA1\u7406:"));
5743
+ console.log(" team-cli config set-token # \u8BBE\u7F6E GitLab Access Token");
5744
+ console.log(" team-cli config show # \u663E\u793A\u5F53\u524D\u914D\u7F6E");
5745
+ console.log(" team-cli init --tag v1.0.0 # \u4F7F\u7528\u6307\u5B9A tag \u521D\u59CB\u5316");
5746
+ console.log(" team-cli update --tag v1.1.0 # \u66F4\u65B0\u5230\u6307\u5B9A\u7248\u672C");
5747
+ console.log(" team-cli diff # \u5BF9\u6BD4\u6A21\u677F\u5DEE\u5F02");
5748
+ console.log("");
5749
+ console.log(chalk4.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
4312
5750
  console.log("");
4313
5751
  }
4314
5752
  process.on("uncaughtException", (error) => {