yg-team-cli 2.0.0 → 2.1.1

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/cli.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
4
  var __esm = (fn, res) => function __init() {
4
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
6
  };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
6
11
 
7
12
  // node_modules/tsup/assets/esm_shims.js
8
13
  import path from "path";
@@ -177,6 +182,238 @@ var init_logger = __esm({
177
182
  }
178
183
  });
179
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
+ };
414
+ }
415
+ });
416
+
180
417
  // src/lib/claude.ts
181
418
  import { execa } from "execa";
182
419
  var ClaudeAI, claudeAI;
@@ -185,6 +422,7 @@ var init_claude = __esm({
185
422
  "use strict";
186
423
  init_esm_shims();
187
424
  init_logger();
425
+ init_utils();
188
426
  ClaudeAI = class {
189
427
  verbose;
190
428
  constructor(verbose = false) {
@@ -218,25 +456,45 @@ var init_claude = __esm({
218
456
  async prompt(promptText, options) {
219
457
  const spinner = logger.startLoading("\u6B63\u5728\u8C03\u7528 Claude...");
220
458
  try {
221
- const args = ["--no-confirm", "-p", promptText];
459
+ const args = [];
460
+ const validContextFiles = [];
222
461
  if (options?.contextFiles && options.contextFiles.length > 0) {
223
462
  for (const file of options.contextFiles) {
224
- args.unshift("--context", file);
463
+ const exists = await FileUtils.exists(file);
464
+ if (exists) {
465
+ validContextFiles.push(file);
466
+ } else {
467
+ logger.warn(`\u4E0A\u4E0B\u6587\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${file}`);
468
+ }
469
+ }
470
+ for (const file of validContextFiles) {
471
+ args.push("--context", file);
225
472
  }
226
473
  }
227
- const { stdout } = await execa("claude", args, {
474
+ args.push("--no-confirm", "-p", promptText);
475
+ const result = await execa("claude", args, {
228
476
  stdio: this.verbose ? "inherit" : "pipe",
229
- timeout: options?.timeout || 3e5
477
+ timeout: options?.timeout || 3e5,
230
478
  // 默认 5 分钟
479
+ reject: false
480
+ // 不自动拒绝非零退出码
231
481
  });
232
482
  spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
233
- return stdout;
483
+ if (result.exitCode !== 0 && !result.stdout) {
484
+ const stderr = result.stderr || "";
485
+ throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})${stderr ? `
486
+ ${stderr}` : ""}`);
487
+ }
488
+ return result.stdout || "";
234
489
  } catch (error) {
235
490
  spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
236
491
  if (error.killed && error.signal === "SIGTERM") {
237
492
  throw new Error("Claude \u6267\u884C\u8D85\u65F6");
238
493
  }
239
- throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}`);
494
+ const stderr = error.stderr || "";
495
+ const exitCode = error.exitCode !== void 0 ? ` (\u9000\u51FA\u7801: ${error.exitCode})` : "";
496
+ throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}${exitCode}${stderr ? `
497
+ ${stderr}` : ""}`);
240
498
  }
241
499
  }
242
500
  /**
@@ -260,12 +518,17 @@ var init_claude = __esm({
260
518
  const role = msg.role === "system" ? "\u7CFB\u7EDF\u6307\u4EE4" : `${msg.role === "user" ? "\u7528\u6237" : "\u52A9\u624B"}`;
261
519
  return `[${role}]: ${msg.content}`;
262
520
  }).join("\n\n");
263
- const { stdout } = await execa("claude", ["--no-confirm", "-p", fullPrompt], {
521
+ const args = ["--no-confirm", "-p", fullPrompt];
522
+ const result = await execa("claude", args, {
264
523
  stdio: this.verbose ? "inherit" : "pipe",
265
- timeout: options?.timeout || 3e5
524
+ timeout: options?.timeout || 3e5,
525
+ reject: false
266
526
  });
267
527
  spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
268
- return stdout;
528
+ if (result.exitCode !== 0 && !result.stdout) {
529
+ throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})`);
530
+ }
531
+ return result.stdout || "";
269
532
  } catch (error) {
270
533
  spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
271
534
  throw error;
@@ -478,232 +741,556 @@ ${projectContext}
478
741
  }
479
742
  });
480
743
 
481
- // src/lib/utils.ts
482
- import fs from "fs-extra";
483
- import path2 from "path";
484
- import { glob } from "glob";
485
- var FileUtils, StringUtils2, DateUtils, GitUtils, SpecUtils;
486
- var init_utils = __esm({
487
- "src/lib/utils.ts"() {
744
+ // src/lib/template-version.ts
745
+ import { execa as execa2 } from "execa";
746
+ import path3 from "path";
747
+ function getConfigPath(projectPath) {
748
+ return path3.join(projectPath, ".team-cli", "template.json");
749
+ }
750
+ async function readTemplateConfig(projectPath) {
751
+ const configPath = getConfigPath(projectPath);
752
+ const exists = await FileUtils.exists(configPath);
753
+ if (!exists) {
754
+ return null;
755
+ }
756
+ try {
757
+ const content = await FileUtils.read(configPath);
758
+ return JSON.parse(content);
759
+ } catch {
760
+ return null;
761
+ }
762
+ }
763
+ async function saveTemplateConfig(projectPath, config) {
764
+ const configDir = path3.join(projectPath, ".team-cli");
765
+ await FileUtils.ensureDir(configDir);
766
+ const configPath = getConfigPath(projectPath);
767
+ const content = JSON.stringify(config, null, 2);
768
+ await FileUtils.write(configPath, content);
769
+ }
770
+ async function initTemplateConfig(projectPath) {
771
+ const config = await readTemplateConfig(projectPath);
772
+ if (config) {
773
+ return;
774
+ }
775
+ await saveTemplateConfig(projectPath, DEFAULT_TEMPLATES);
776
+ }
777
+ async function getLatestCommit(repository) {
778
+ try {
779
+ const { stdout } = await execa2("git", ["ls-remote", repository, "HEAD"], {
780
+ stdio: "pipe"
781
+ });
782
+ const commit = stdout.split(" ")[0];
783
+ return commit;
784
+ } catch (error) {
785
+ logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 commit \u5931\u8D25: ${error}`);
786
+ return "";
787
+ }
788
+ }
789
+ async function getLatestTag(repository) {
790
+ try {
791
+ const { stdout } = await execa2(
792
+ "git",
793
+ ["ls-remote", "--tags", repository],
794
+ {
795
+ stdio: "pipe"
796
+ }
797
+ );
798
+ const tags = stdout.split("\n").filter((line) => line.includes("refs/tags/") && !line.includes("^{}")).map((line) => {
799
+ const [, ref] = line.split(" ");
800
+ return ref.replace("refs/tags/", "");
801
+ }).filter((tag) => /^v?\d+\.\d+\.\d+/.test(tag)).sort((a, b) => {
802
+ const va = a.replace(/^v/, "").split(".").map(Number);
803
+ const vb = b.replace(/^v/, "").split(".").map(Number);
804
+ for (let i = 0; i < 3; i++) {
805
+ if ((va[i] || 0) > (vb[i] || 0)) return 1;
806
+ if ((va[i] || 0) < (vb[i] || 0)) return -1;
807
+ }
808
+ return 0;
809
+ });
810
+ return tags[tags.length - 1] || "";
811
+ } catch (error) {
812
+ logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 tag \u5931\u8D25: ${error}`);
813
+ return "";
814
+ }
815
+ }
816
+ async function checkTemplateUpdate(projectPath, type) {
817
+ const config = await readTemplateConfig(projectPath);
818
+ if (!config || !config[type]) {
819
+ return null;
820
+ }
821
+ const repository = config[type].repository;
822
+ const currentCommit = config[type].commit;
823
+ const currentTag = config[type].tag;
824
+ const latestCommit = await getLatestCommit(repository);
825
+ const latestTag = await getLatestTag(repository);
826
+ if (!latestCommit) {
827
+ return null;
828
+ }
829
+ const needsUpdate = currentCommit && currentCommit !== latestCommit;
830
+ return {
831
+ type,
832
+ repository,
833
+ currentCommit,
834
+ currentTag,
835
+ latestCommit,
836
+ latestTag,
837
+ needsUpdate: needsUpdate || false
838
+ };
839
+ }
840
+ async function updateTemplateVersion(projectPath, type, commit, options) {
841
+ const config = await readTemplateConfig(projectPath);
842
+ if (!config) {
843
+ return;
844
+ }
845
+ config[type].commit = commit;
846
+ if (options?.tag) {
847
+ config[type].tag = options.tag;
848
+ }
849
+ if (options?.branch) {
850
+ config[type].branch = options.branch;
851
+ }
852
+ config[type].lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
853
+ await saveTemplateConfig(projectPath, config);
854
+ }
855
+ var DEFAULT_TEMPLATES;
856
+ var init_template_version = __esm({
857
+ "src/lib/template-version.ts"() {
488
858
  "use strict";
489
859
  init_esm_shims();
490
- FileUtils = class {
860
+ init_utils();
861
+ init_logger();
862
+ DEFAULT_TEMPLATES = {
863
+ frontend: {
864
+ repository: "https://gitlab.yungu-inc.org/static/fe-tpl-ai"
865
+ },
866
+ backend: {
867
+ repository: "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git"
868
+ }
869
+ };
870
+ }
871
+ });
872
+
873
+ // src/lib/user-config.ts
874
+ var user_config_exports = {};
875
+ __export(user_config_exports, {
876
+ UserConfigManager: () => UserConfigManager,
877
+ userConfigManager: () => userConfigManager
878
+ });
879
+ import path4 from "path";
880
+ import os from "os";
881
+ import crypto from "crypto";
882
+ var UserConfigManager, userConfigManager;
883
+ var init_user_config = __esm({
884
+ "src/lib/user-config.ts"() {
885
+ "use strict";
886
+ init_esm_shims();
887
+ init_utils();
888
+ init_logger();
889
+ UserConfigManager = class {
890
+ configPath;
891
+ constructor() {
892
+ const configDir = path4.join(os.homedir(), ".team-cli");
893
+ this.configPath = path4.join(configDir, "config.json");
894
+ }
895
+ /**
896
+ * 加载用户配置
897
+ */
898
+ async load() {
899
+ try {
900
+ const exists = await FileUtils.exists(this.configPath);
901
+ if (!exists) {
902
+ return null;
903
+ }
904
+ const content = await FileUtils.read(this.configPath);
905
+ const config = JSON.parse(content);
906
+ if (config.gitlab?.accessToken) {
907
+ config.gitlab.accessToken = this.decrypt(config.gitlab.accessToken);
908
+ }
909
+ return config;
910
+ } catch (error) {
911
+ logger.debug(`\u52A0\u8F7D\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
912
+ return null;
913
+ }
914
+ }
491
915
  /**
492
- * 确保目录存在
916
+ * 保存用户配置
493
917
  */
494
- static async ensureDir(dir) {
495
- await fs.ensureDir(dir);
918
+ async save(config) {
919
+ try {
920
+ const configDir = path4.dirname(this.configPath);
921
+ await FileUtils.ensureDir(configDir);
922
+ const configToSave = { ...config };
923
+ if (configToSave.gitlab?.accessToken) {
924
+ configToSave.gitlab.accessToken = this.encrypt(configToSave.gitlab.accessToken);
925
+ }
926
+ const content = JSON.stringify(configToSave, null, 2);
927
+ await FileUtils.write(this.configPath, content);
928
+ } catch (error) {
929
+ throw new Error(`\u4FDD\u5B58\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
930
+ }
496
931
  }
497
932
  /**
498
- * 读取文件内容
933
+ * 更新 GitLab Token
499
934
  */
500
- static async read(file) {
501
- return await fs.readFile(file, "utf-8");
935
+ async updateGitLabToken(token, baseUrl) {
936
+ const config = await this.load() || {
937
+ gitlab: {
938
+ accessToken: "",
939
+ baseUrl: "https://gitlab.com",
940
+ timeout: 3e4
941
+ }
942
+ };
943
+ config.gitlab.accessToken = token;
944
+ if (baseUrl) {
945
+ config.gitlab.baseUrl = baseUrl;
946
+ }
947
+ await this.save(config);
502
948
  }
503
949
  /**
504
- * 写入文件内容
950
+ * 获取 GitLab Token
505
951
  */
506
- static async write(file, content) {
507
- await fs.writeFile(file, content, "utf-8");
952
+ async getGitLabToken() {
953
+ const config = await this.load();
954
+ return config?.gitlab?.accessToken || null;
508
955
  }
509
956
  /**
510
- * 检查文件是否存在
957
+ * 获取 GitLab 配置
511
958
  */
512
- static async exists(file) {
513
- return await fs.pathExists(file);
959
+ async getGitLabConfig() {
960
+ const config = await this.load();
961
+ return config?.gitlab || null;
514
962
  }
515
963
  /**
516
- * 复制文件
964
+ * 检查是否已有配置
517
965
  */
518
- static async copy(src, dest) {
519
- await fs.copy(src, dest);
966
+ async hasConfig() {
967
+ const config = await this.load();
968
+ return config !== null && config.gitlab?.accessToken !== void 0;
520
969
  }
521
970
  /**
522
- * 删除文件或目录
971
+ * 删除配置
523
972
  */
524
- static async remove(file) {
525
- await fs.remove(file);
973
+ async removeConfig() {
974
+ try {
975
+ const exists = await FileUtils.exists(this.configPath);
976
+ if (exists) {
977
+ await FileUtils.remove(this.configPath);
978
+ }
979
+ } catch (error) {
980
+ throw new Error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error}`);
981
+ }
526
982
  }
527
983
  /**
528
- * 移动文件
984
+ * 简单加密 (使用机器特定密钥)
529
985
  */
530
- static async move(src, dest) {
531
- await fs.move(src, dest);
986
+ encrypt(text) {
987
+ const key = this.getMachineKey();
988
+ const iv = crypto.randomBytes(16);
989
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
990
+ let encrypted = cipher.update(text, "utf8", "hex");
991
+ encrypted += cipher.final("hex");
992
+ return iv.toString("hex") + ":" + encrypted;
532
993
  }
533
994
  /**
534
- * 使用 glob 查找文件
995
+ * 简单解密
535
996
  */
536
- static async findFiles(pattern, cwd) {
537
- return await glob(pattern, {
538
- cwd,
539
- absolute: false,
540
- nodir: true
541
- });
997
+ decrypt(encryptedText) {
998
+ try {
999
+ const key = this.getMachineKey();
1000
+ const parts = encryptedText.split(":");
1001
+ const iv = Buffer.from(parts[0], "hex");
1002
+ const encrypted = parts[1];
1003
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
1004
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
1005
+ decrypted += decipher.final("utf8");
1006
+ return decrypted;
1007
+ } catch {
1008
+ return encryptedText;
1009
+ }
542
1010
  }
543
1011
  /**
544
- * 读取 JSON 文件
1012
+ * 获取机器特定密钥
545
1013
  */
546
- static async readJson(file) {
547
- return await fs.readJson(file);
1014
+ getMachineKey() {
1015
+ const hostname = os.hostname();
1016
+ const platform = os.platform();
1017
+ const arch = os.arch();
1018
+ const cpus = os.cpus();
1019
+ const machineInfo = `${hostname}-${platform}-${arch}-${cpus[0]?.model || "unknown"}`;
1020
+ return crypto.createHash("sha256").update(machineInfo).digest();
548
1021
  }
549
1022
  /**
550
- * 写入 JSON 文件
1023
+ * 获取配置目录
551
1024
  */
552
- static async writeJson(file, data) {
553
- await fs.writeJson(file, data, { spaces: 2 });
1025
+ getConfigDir() {
1026
+ return path4.dirname(this.configPath);
554
1027
  }
555
- };
556
- StringUtils2 = class {
557
1028
  /**
558
- * 转换为 kebab-case
1029
+ * 获取配置文件路径
559
1030
  */
560
- static toKebabCase(str) {
561
- return str.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1031
+ getConfigPath() {
1032
+ return this.configPath;
1033
+ }
1034
+ };
1035
+ userConfigManager = new UserConfigManager();
1036
+ }
1037
+ });
1038
+
1039
+ // src/lib/gitlab-api.ts
1040
+ var gitlab_api_exports = {};
1041
+ __export(gitlab_api_exports, {
1042
+ GitLabAPI: () => GitLabAPI
1043
+ });
1044
+ var GitLabAPI;
1045
+ var init_gitlab_api = __esm({
1046
+ "src/lib/gitlab-api.ts"() {
1047
+ "use strict";
1048
+ init_esm_shims();
1049
+ GitLabAPI = class {
1050
+ config;
1051
+ defaultTimeout = 3e4;
1052
+ constructor(config) {
1053
+ this.config = {
1054
+ ...config,
1055
+ timeout: config.timeout || this.defaultTimeout
1056
+ };
562
1057
  }
563
1058
  /**
564
- * 转换为 PascalCase
1059
+ * 验证 Token 是否有效
565
1060
  */
566
- static toPascalCase(str) {
567
- return str.replace(/[-_\s](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
1061
+ async authenticate() {
1062
+ try {
1063
+ const response = await this.request("/user");
1064
+ return response.ok;
1065
+ } catch (error) {
1066
+ return false;
1067
+ }
568
1068
  }
569
1069
  /**
570
- * 转换为 camelCase
1070
+ * 列出项目的所有 Tags
571
1071
  */
572
- static toCamelCase(str) {
573
- const pascal = this.toPascalCase(str);
574
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
1072
+ async listTags(projectPath) {
1073
+ try {
1074
+ const encodedPath = this.encodeProjectPath(projectPath);
1075
+ const response = await this.request(`/projects/${encodedPath}/repository/tags?per_page=100`);
1076
+ if (!response.ok) {
1077
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
1078
+ }
1079
+ const tags = await response.json();
1080
+ return tags.sort((a, b) => {
1081
+ return this.compareVersionStrings(b.name, a.name);
1082
+ });
1083
+ } catch (error) {
1084
+ throw new Error(`\u83B7\u53D6 Tags \u5931\u8D25: ${error}`);
1085
+ }
575
1086
  }
576
1087
  /**
577
- * 转换为 snake_case
1088
+ * 列出项目的所有 Branches
578
1089
  */
579
- static toSnakeCase(str) {
580
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/^_/, "").replace(/-+/g, "_").replace(/[\s]+/g, "_");
1090
+ async listBranches(projectPath) {
1091
+ try {
1092
+ const encodedPath = this.encodeProjectPath(projectPath);
1093
+ const response = await this.request(`/projects/${encodedPath}/repository/branches?per_page=100`);
1094
+ if (!response.ok) {
1095
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
1096
+ }
1097
+ const branches = await response.json();
1098
+ return branches.sort((a, b) => {
1099
+ if (a.default) return -1;
1100
+ if (b.default) return 1;
1101
+ return a.name.localeCompare(b.name);
1102
+ });
1103
+ } catch (error) {
1104
+ throw new Error(`\u83B7\u53D6 Branches \u5931\u8D25: ${error}`);
1105
+ }
581
1106
  }
582
1107
  /**
583
- * 首字母大写
1108
+ * 验证 Tag 是否存在
584
1109
  */
585
- static capitalize(str) {
586
- return str.charAt(0).toUpperCase() + str.slice(1);
1110
+ async validateTag(projectPath, tag) {
1111
+ try {
1112
+ const tags = await this.listTags(projectPath);
1113
+ return tags.some((t) => t.name === tag);
1114
+ } catch {
1115
+ return false;
1116
+ }
587
1117
  }
588
1118
  /**
589
- * 截断字符串
1119
+ * 验证 Branch 是否存在
590
1120
  */
591
- static truncate(str, length, suffix = "...") {
592
- if (str.length <= length) return str;
593
- return str.slice(0, length - suffix.length) + suffix;
1121
+ async validateBranch(projectPath, branch) {
1122
+ try {
1123
+ const branches = await this.listBranches(projectPath);
1124
+ return branches.some((b) => b.name === branch);
1125
+ } catch {
1126
+ return false;
1127
+ }
594
1128
  }
595
1129
  /**
596
- * 生成 slug
1130
+ * 获取项目信息
597
1131
  */
598
- static slugify(str) {
599
- return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
1132
+ async getProject(projectPath) {
1133
+ try {
1134
+ const encodedPath = this.encodeProjectPath(projectPath);
1135
+ const response = await this.request(`/projects/${encodedPath}`);
1136
+ if (!response.ok) {
1137
+ return null;
1138
+ }
1139
+ return await response.json();
1140
+ } catch {
1141
+ return null;
1142
+ }
600
1143
  }
601
- };
602
- DateUtils = class _DateUtils {
603
1144
  /**
604
- * 格式化日期
1145
+ * 获取指定 Tag 的 commit 信息
605
1146
  */
606
- static format(date = /* @__PURE__ */ new Date(), format = "YYYY-MM-DD HH:mm:ss") {
607
- const year = date.getFullYear();
608
- const month = String(date.getMonth() + 1).padStart(2, "0");
609
- const day = String(date.getDate()).padStart(2, "0");
610
- const hours = String(date.getHours()).padStart(2, "0");
611
- const minutes = String(date.getMinutes()).padStart(2, "0");
612
- const seconds = String(date.getSeconds()).padStart(2, "0");
613
- return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
1147
+ async getTagCommit(projectPath, tag) {
1148
+ try {
1149
+ const tags = await this.listTags(projectPath);
1150
+ const targetTag = tags.find((t) => t.name === tag);
1151
+ return targetTag?.commit.id || null;
1152
+ } catch {
1153
+ return null;
1154
+ }
614
1155
  }
615
1156
  /**
616
- * 获取相对时间
1157
+ * 获取指定 Branch 的最新 commit 信息
617
1158
  */
618
- static relative(date) {
619
- const now = /* @__PURE__ */ new Date();
620
- const diff = now.getTime() - date.getTime();
621
- const seconds = Math.floor(diff / 1e3);
622
- const minutes = Math.floor(seconds / 60);
623
- const hours = Math.floor(minutes / 60);
624
- const days = Math.floor(hours / 24);
625
- if (days > 7) {
626
- return _DateUtils.format(date, "YYYY-MM-DD");
627
- } else if (days > 0) {
628
- return `${days} \u5929\u524D`;
629
- } else if (hours > 0) {
630
- return `${hours} \u5C0F\u65F6\u524D`;
631
- } else if (minutes > 0) {
632
- return `${minutes} \u5206\u949F\u524D`;
633
- } else {
634
- return "\u521A\u521A";
1159
+ async getBranchCommit(projectPath, branch) {
1160
+ try {
1161
+ const encodedPath = this.encodeProjectPath(projectPath);
1162
+ const response = await this.request(
1163
+ `/projects/${encodedPath}/repository/branches/${encodeURIComponent(branch)}`
1164
+ );
1165
+ if (!response.ok) {
1166
+ return null;
1167
+ }
1168
+ const branchInfo = await response.json();
1169
+ return branchInfo.commit.id;
1170
+ } catch {
1171
+ return null;
635
1172
  }
636
1173
  }
637
- };
638
- GitUtils = class {
639
1174
  /**
640
- * 检查是否在 Git 仓库中
1175
+ * 对比两个版本之间的差异
641
1176
  */
642
- static async isGitRepo(cwd = process.cwd()) {
643
- const gitDir = path2.join(cwd, ".git");
644
- return await FileUtils.exists(gitDir);
1177
+ async compareVersions(projectPath, from, to) {
1178
+ try {
1179
+ const encodedPath = this.encodeProjectPath(projectPath);
1180
+ const response = await this.request(
1181
+ `/projects/${encodedPath}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
1182
+ );
1183
+ if (!response.ok) {
1184
+ return null;
1185
+ }
1186
+ return await response.json();
1187
+ } catch {
1188
+ return null;
1189
+ }
645
1190
  }
646
1191
  /**
647
- * 获取当前分支名
1192
+ * 比较两个版本号(用于排序)
1193
+ * 返回值: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
648
1194
  */
649
- static async getCurrentBranch(cwd = process.cwd()) {
650
- const { execa: execa3 } = await import("execa");
651
- const { stdout } = await execa3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
652
- cwd
653
- });
654
- return stdout.trim();
1195
+ compareVersionStrings(v1, v2) {
1196
+ const version1 = v1.replace(/^v/, "");
1197
+ const version2 = v2.replace(/^v/, "");
1198
+ const parts1 = version1.split(".").map((p) => parseInt(p, 10) || 0);
1199
+ const parts2 = version2.split(".").map((p) => parseInt(p, 10) || 0);
1200
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1201
+ const p1 = parts1[i] || 0;
1202
+ const p2 = parts2[i] || 0;
1203
+ if (p1 > p2) return 1;
1204
+ if (p1 < p2) return -1;
1205
+ }
1206
+ return 0;
655
1207
  }
656
1208
  /**
657
- * 获取当前 commit hash
1209
+ * 获取最新的版本(优先 Tag,否则 latest commit
658
1210
  */
659
- static async getCurrentCommit(cwd = process.cwd()) {
660
- const { execa: execa3 } = await import("execa");
661
- const { stdout } = await execa3("git", ["rev-parse", "HEAD"], { cwd });
662
- return stdout.trim().slice(0, 7);
1211
+ async getLatestVersion(projectPath) {
1212
+ try {
1213
+ const tags = await this.listTags(projectPath);
1214
+ const branches = await this.listBranches(projectPath);
1215
+ if (tags.length > 0) {
1216
+ const latestTag = tags[0];
1217
+ return {
1218
+ type: "tag",
1219
+ name: latestTag.name,
1220
+ commit: latestTag.commit.id
1221
+ };
1222
+ }
1223
+ const defaultBranch = branches.find((b) => b.default);
1224
+ if (defaultBranch) {
1225
+ return {
1226
+ type: "commit",
1227
+ name: defaultBranch.name,
1228
+ commit: defaultBranch.commit.id
1229
+ };
1230
+ }
1231
+ return null;
1232
+ } catch {
1233
+ return null;
1234
+ }
663
1235
  }
664
- };
665
- SpecUtils = class {
666
1236
  /**
667
- * 解析 spec 文件
1237
+ * 解析项目路径
1238
+ * 从 Git URL 中提取项目路径
668
1239
  */
669
- static async parseSpec(file) {
670
- const content = await FileUtils.read(file);
671
- const spec = {};
672
- const titleMatch = content.match(/^#\s+(.+)$/m);
673
- if (titleMatch) {
674
- spec.title = titleMatch[1];
675
- }
676
- const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
677
- if (nameMatch) {
678
- spec.name = nameMatch[1];
679
- }
680
- const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
681
- if (priorityMatch) {
682
- spec.priority = priorityMatch[1];
683
- }
684
- const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
685
- if (statusMatch) {
686
- spec.status = statusMatch[1];
1240
+ static parseProjectPath(repository) {
1241
+ let path17 = repository;
1242
+ path17 = path17.replace(/^https?:\/\//, "");
1243
+ path17 = path17.replace(/^git@/, "");
1244
+ const parts = path17.split("/");
1245
+ if (parts.length > 1) {
1246
+ path17 = parts.slice(1).join("/");
687
1247
  }
688
- const depSection = content.match(/## 依赖关系\s+([\s\S]+?)##/m);
689
- if (depSection) {
690
- const depMatches = depSection[1].matchAll(/- \[([ x])\]\s*(.+)$/gm);
691
- spec.dependencies = Array.from(depMatches).map((m) => ({
692
- completed: m[1] === "x",
693
- name: m[2]
694
- }));
1248
+ path17 = path17.replace(/\.git$/, "");
1249
+ return path17;
1250
+ }
1251
+ /**
1252
+ * 编码项目路径用于 API 请求
1253
+ */
1254
+ encodeProjectPath(projectPath) {
1255
+ return encodeURIComponent(projectPath).replace(/%2F/g, "%2F");
1256
+ }
1257
+ /**
1258
+ * 发送 HTTP 请求
1259
+ */
1260
+ async request(endpoint, options = {}) {
1261
+ const url = `${this.config.baseUrl}/api/v4${endpoint}`;
1262
+ const headers = {
1263
+ "PRIVATE-TOKEN": this.config.accessToken,
1264
+ "Content-Type": "application/json",
1265
+ ...options.headers
1266
+ };
1267
+ const controller = new AbortController();
1268
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
1269
+ try {
1270
+ const response = await fetch(url, {
1271
+ ...options,
1272
+ headers,
1273
+ signal: controller.signal
1274
+ });
1275
+ return response;
1276
+ } catch (error) {
1277
+ if (error.name === "AbortError") {
1278
+ throw new Error("\u8BF7\u6C42\u8D85\u65F6");
1279
+ }
1280
+ throw error;
1281
+ } finally {
1282
+ clearTimeout(timeoutId);
695
1283
  }
696
- return spec;
697
1284
  }
698
1285
  /**
699
- * 获取 spec 状态
1286
+ * 获取默认分支
700
1287
  */
701
- static async getSpecStatus(file) {
1288
+ async getDefaultBranch(projectPath) {
702
1289
  try {
703
- const spec = await this.parseSpec(file);
704
- return spec.status || "\u672A\u77E5";
1290
+ const project = await this.getProject(projectPath);
1291
+ return project?.default_branch || null;
705
1292
  } catch {
706
- return "\u9519\u8BEF";
1293
+ return null;
707
1294
  }
708
1295
  }
709
1296
  };
@@ -713,7 +1300,7 @@ var init_utils = __esm({
713
1300
  // src/commands/init.ts
714
1301
  import { Command } from "commander";
715
1302
  import inquirer from "inquirer";
716
- import path3 from "path";
1303
+ import path5 from "path";
717
1304
  import fs2 from "fs-extra";
718
1305
  import { Listr } from "listr2";
719
1306
  async function generateTechStack(projectPath) {
@@ -812,7 +1399,7 @@ docs/
812
1399
  \u2514\u2500\u2500 sessions/ # \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
813
1400
  \`\`\`
814
1401
  `;
815
- await FileUtils.write(path3.join(projectPath, "TECH_STACK.md"), content);
1402
+ await FileUtils.write(path5.join(projectPath, "TECH_STACK.md"), content);
816
1403
  }
817
1404
  async function generateConventions(projectPath) {
818
1405
  const content = `# \u5F00\u53D1\u89C4\u8303
@@ -1115,7 +1702,7 @@ test('renders user name', () => {
1115
1702
  });
1116
1703
  \`\`\`
1117
1704
  `;
1118
- await FileUtils.write(path3.join(projectPath, "CONVENTIONS.md"), content);
1705
+ await FileUtils.write(path5.join(projectPath, "CONVENTIONS.md"), content);
1119
1706
  }
1120
1707
  async function generateAIMemory(projectPath, projectName) {
1121
1708
  const content = `# AI Memory - \u9879\u76EE\u72B6\u6001\u8BB0\u5F55
@@ -1126,6 +1713,15 @@ async function generateAIMemory(projectPath, projectName) {
1126
1713
  - **\u5F53\u524D\u9636\u6BB5**: \u521D\u59CB\u5316
1127
1714
  - **\u6700\u540E\u66F4\u65B0**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
1128
1715
 
1716
+ ## \u6A21\u677F\u7248\u672C\u4FE1\u606F
1717
+
1718
+ > \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u7BA1\u7406\uFF0C\u8BB0\u5F55\u4F7F\u7528\u7684\u524D\u540E\u7AEF\u6A21\u677F\u7248\u672C
1719
+
1720
+ | \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |
1721
+ |------|------|-----|--------|--------|----------|
1722
+ | \u524D\u7AEF | - | - | - | - | - |
1723
+ | \u540E\u7AEF | - | - | - | - | - |
1724
+
1129
1725
  ## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
1130
1726
 
1131
1727
  | \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
@@ -1165,7 +1761,7 @@ async function generateAIMemory(projectPath, projectName) {
1165
1761
  | Bug ID | \u65E5\u671F | \u95EE\u9898\u63CF\u8FF0 | \u72B6\u6001 |
1166
1762
  |--------|------|---------|------|
1167
1763
  `;
1168
- await FileUtils.write(path3.join(projectPath, "AI_MEMORY.md"), content);
1764
+ await FileUtils.write(path5.join(projectPath, "AI_MEMORY.md"), content);
1169
1765
  }
1170
1766
  async function generateSpecTemplate(projectPath) {
1171
1767
  const content = `# [\u529F\u80FD\u6807\u9898]
@@ -1229,35 +1825,78 @@ async function generateSpecTemplate(projectPath) {
1229
1825
  ----
1230
1826
  *\u751F\u6210\u4E8E: {{TIMESTAMP}} by team-cli*
1231
1827
  `;
1232
- await FileUtils.write(path3.join(projectPath, "docs/specs/template.md"), content);
1828
+ await FileUtils.write(path5.join(projectPath, "docs/specs/template.md"), content);
1233
1829
  }
1234
- async function cloneBackendTemplate(projectPath) {
1830
+ async function cloneBackendTemplate(projectPath, versionOptions) {
1235
1831
  const templateRepo = process.env.TEMPLATE_REPO || "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git";
1236
- const backendPath = path3.join(projectPath, "backend");
1832
+ const backendPath = path5.join(projectPath, "backend");
1237
1833
  try {
1238
1834
  const { execaCommand } = await import("execa");
1239
- const tempDir = path3.join(projectPath, ".template-temp");
1240
- await execaCommand(`git clone --depth=1 ${templateRepo} ${tempDir}`, {
1835
+ const tempDir = path5.join(projectPath, ".template-temp");
1836
+ if (versionOptions?.tag || versionOptions?.branch) {
1837
+ const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
1838
+ const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
1839
+ const config = await userConfigManager2.getGitLabConfig();
1840
+ if (config) {
1841
+ const gitlabAPI = new GitLabAPI2(config);
1842
+ const projectPathEncoded = GitLabAPI2.parseProjectPath(templateRepo);
1843
+ if (versionOptions.tag) {
1844
+ const isValid = await gitlabAPI.validateTag(projectPathEncoded, versionOptions.tag);
1845
+ if (!isValid) {
1846
+ logger.error(`\u540E\u7AEF\u6A21\u677F tag "${versionOptions.tag}" \u4E0D\u5B58\u5728`);
1847
+ process.exit(1);
1848
+ }
1849
+ logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F tag: ${versionOptions.tag}`);
1850
+ }
1851
+ if (versionOptions.branch) {
1852
+ const isValid = await gitlabAPI.validateBranch(projectPathEncoded, versionOptions.branch);
1853
+ if (!isValid) {
1854
+ logger.error(`\u540E\u7AEF\u6A21\u677F\u5206\u652F "${versionOptions.branch}" \u4E0D\u5B58\u5728`);
1855
+ process.exit(1);
1856
+ }
1857
+ logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F\u5206\u652F: ${versionOptions.branch}`);
1858
+ }
1859
+ } else {
1860
+ logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
1861
+ }
1862
+ }
1863
+ const ref = versionOptions?.tag || versionOptions?.branch || "HEAD";
1864
+ await execaCommand(`git clone --depth=1 --branch ${ref} ${templateRepo} ${tempDir}`, {
1241
1865
  stdio: "inherit",
1242
1866
  timeout: 6e4
1243
1867
  });
1868
+ const { execa: e } = await import("execa");
1869
+ const { stdout: commit } = await e("git", ["rev-parse", "HEAD"], {
1870
+ cwd: tempDir,
1871
+ stdio: "pipe"
1872
+ });
1873
+ const { stdout: tags } = await e("git", ["tag", "-l", "--sort=-v:refname"], {
1874
+ cwd: tempDir,
1875
+ stdio: "pipe"
1876
+ });
1877
+ const latestTag = tags.split("\n")[0] || void 0;
1244
1878
  await fs2.copy(tempDir, backendPath, {
1245
1879
  filter: (src) => !src.includes(".git")
1246
1880
  });
1247
1881
  await fs2.remove(tempDir);
1248
- const gitDir = path3.join(backendPath, ".git");
1882
+ const gitDir = path5.join(backendPath, ".git");
1249
1883
  if (await FileUtils.exists(gitDir)) {
1250
1884
  await FileUtils.remove(gitDir);
1251
1885
  }
1886
+ await initTemplateConfig(projectPath);
1887
+ await updateTemplateVersion(projectPath, "backend", commit.trim(), {
1888
+ tag: versionOptions?.tag || latestTag,
1889
+ branch: versionOptions?.branch
1890
+ });
1252
1891
  } catch (error) {
1253
1892
  logger.warn("\u514B\u9686\u540E\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1254
- await FileUtils.ensureDir(path3.join(backendPath, "src/main/java/com/example"));
1255
- await FileUtils.ensureDir(path3.join(backendPath, "src/main/resources"));
1256
- await FileUtils.ensureDir(path3.join(backendPath, "src/test/java"));
1893
+ await FileUtils.ensureDir(path5.join(backendPath, "src/main/java/com/example"));
1894
+ await FileUtils.ensureDir(path5.join(backendPath, "src/main/resources"));
1895
+ await FileUtils.ensureDir(path5.join(backendPath, "src/test/java"));
1257
1896
  }
1258
1897
  }
1259
1898
  async function generateFrontendScaffold(projectPath) {
1260
- const frontendPath = path3.join(projectPath, "frontend");
1899
+ const frontendPath = path5.join(projectPath, "frontend");
1261
1900
  try {
1262
1901
  const prompt = `Read TECH_STACK.md and CONVENTIONS.md.
1263
1902
  Initialize a Next.js 14 frontend in ./frontend with:
@@ -1270,16 +1909,16 @@ Initialize a Next.js 14 frontend in ./frontend with:
1270
1909
  Do not run any servers, just generate the folder structure and configuration files.`;
1271
1910
  await claudeAI.prompt(prompt, {
1272
1911
  contextFiles: [
1273
- path3.join(projectPath, "TECH_STACK.md"),
1274
- path3.join(projectPath, "CONVENTIONS.md")
1912
+ path5.join(projectPath, "TECH_STACK.md"),
1913
+ path5.join(projectPath, "CONVENTIONS.md")
1275
1914
  ]
1276
1915
  });
1277
1916
  } catch (error) {
1278
1917
  logger.warn("Claude \u751F\u6210\u524D\u7AEF\u5931\u8D25\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1279
- await FileUtils.ensureDir(path3.join(frontendPath, "src/app"));
1280
- await FileUtils.ensureDir(path3.join(frontendPath, "src/components"));
1281
- await FileUtils.ensureDir(path3.join(frontendPath, "src/lib"));
1282
- await FileUtils.ensureDir(path3.join(frontendPath, "public"));
1918
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/app"));
1919
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/components"));
1920
+ await FileUtils.ensureDir(path5.join(frontendPath, "src/lib"));
1921
+ await FileUtils.ensureDir(path5.join(frontendPath, "public"));
1283
1922
  }
1284
1923
  }
1285
1924
  async function generateDockerFiles(projectPath) {
@@ -1307,7 +1946,8 @@ var init_init = __esm({
1307
1946
  init_logger();
1308
1947
  init_claude();
1309
1948
  init_utils();
1310
- 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) => {
1949
+ init_template_version();
1950
+ 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) => {
1311
1951
  try {
1312
1952
  if (!projectName) {
1313
1953
  const answers = await inquirer.prompt([
@@ -1338,7 +1978,7 @@ var init_init = __esm({
1338
1978
  logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
1339
1979
  process.exit(1);
1340
1980
  }
1341
- const projectPath = path3.resolve(options.dir, projectName);
1981
+ const projectPath = path5.resolve(options.dir, projectName);
1342
1982
  if (await FileUtils.exists(projectPath)) {
1343
1983
  logger.error(`\u76EE\u5F55\u5DF2\u5B58\u5728: ${projectPath}`);
1344
1984
  logger.info("\u8BF7\u9009\u62E9\u5176\u4ED6\u9879\u76EE\u540D\u79F0\u6216\u5220\u9664\u73B0\u6709\u76EE\u5F55");
@@ -1357,11 +1997,11 @@ var init_init = __esm({
1357
1997
  title: "\u521B\u5EFA\u9879\u76EE\u76EE\u5F55",
1358
1998
  task: async () => {
1359
1999
  await FileUtils.ensureDir(projectPath);
1360
- await FileUtils.ensureDir(path3.join(projectPath, "frontend"));
1361
- await FileUtils.ensureDir(path3.join(projectPath, "backend"));
1362
- await FileUtils.ensureDir(path3.join(projectPath, "docs/specs"));
1363
- await FileUtils.ensureDir(path3.join(projectPath, "docs/api"));
1364
- await FileUtils.ensureDir(path3.join(projectPath, "docs/sessions"));
2000
+ await FileUtils.ensureDir(path5.join(projectPath, "frontend"));
2001
+ await FileUtils.ensureDir(path5.join(projectPath, "backend"));
2002
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/specs"));
2003
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/api"));
2004
+ await FileUtils.ensureDir(path5.join(projectPath, "docs/sessions"));
1365
2005
  }
1366
2006
  },
1367
2007
  {
@@ -1391,7 +2031,12 @@ var init_init = __esm({
1391
2031
  {
1392
2032
  title: "\u514B\u9686\u540E\u7AEF\u6A21\u677F",
1393
2033
  task: async () => {
1394
- await cloneBackendTemplate(projectPath);
2034
+ const backendTag = options.backendTag || options.tag;
2035
+ const backendBranch = options.backendBranch;
2036
+ await cloneBackendTemplate(projectPath, {
2037
+ tag: backendTag,
2038
+ branch: backendBranch
2039
+ });
1395
2040
  }
1396
2041
  },
1397
2042
  {
@@ -1446,7 +2091,7 @@ var init_init = __esm({
1446
2091
  // src/commands/breakdown.ts
1447
2092
  import { Command as Command2 } from "commander";
1448
2093
  import inquirer2 from "inquirer";
1449
- import path4 from "path";
2094
+ import path6 from "path";
1450
2095
  import { Listr as Listr2 } from "listr2";
1451
2096
  function buildBreakdownPrompt(specContent) {
1452
2097
  return `Role: Senior Technical Lead and Agile Coach
@@ -1561,9 +2206,9 @@ var init_breakdown = __esm({
1561
2206
  choices: ctx.specs
1562
2207
  }
1563
2208
  ]);
1564
- return path4.join("docs/specs", selectedFile);
2209
+ return path6.join("docs/specs", selectedFile);
1565
2210
  }
1566
- const fullPath = specFile.startsWith("docs/specs/") ? specFile : path4.join("docs/specs", specFile);
2211
+ const fullPath = specFile.startsWith("docs/specs/") ? specFile : path6.join("docs/specs", specFile);
1567
2212
  const exists = await FileUtils.exists(fullPath);
1568
2213
  if (!exists) {
1569
2214
  throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
@@ -1633,7 +2278,7 @@ var init_breakdown = __esm({
1633
2278
  // src/commands/dev.ts
1634
2279
  import { Command as Command3 } from "commander";
1635
2280
  import inquirer3 from "inquirer";
1636
- import path5 from "path";
2281
+ import path7 from "path";
1637
2282
  async function selectSpec() {
1638
2283
  logger.step("\u6B65\u9AA4 1/3: \u9009\u62E9 spec \u6587\u4EF6...");
1639
2284
  logger.newLine();
@@ -1649,7 +2294,7 @@ async function selectSpec() {
1649
2294
  }
1650
2295
  const specs = [];
1651
2296
  for (let i = 0; i < specFiles.length; i++) {
1652
- const file = path5.join(specDir, specFiles[i]);
2297
+ const file = path7.join(specDir, specFiles[i]);
1653
2298
  const spec = await FileUtils.read(file);
1654
2299
  const status = parseSpecStatus(spec);
1655
2300
  const dependencies = parseDependencies(spec);
@@ -1963,8 +2608,8 @@ async function generateSessionLog(specFile, milestone, todo, taskDescription, re
1963
2608
  const sessionDir = "docs/sessions";
1964
2609
  await FileUtils.ensureDir(sessionDir);
1965
2610
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1966
- const specName = path5.basename(specFile, ".md");
1967
- const logFile = path5.join(sessionDir, `${timestamp}_${specName}.md`);
2611
+ const specName = path7.basename(specFile, ".md");
2612
+ const logFile = path7.join(sessionDir, `${timestamp}_${specName}.md`);
1968
2613
  const content = `# \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
1969
2614
 
1970
2615
  **\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
@@ -2037,7 +2682,7 @@ var init_dev = __esm({
2037
2682
  // src/commands/add-feature.ts
2038
2683
  import { Command as Command4 } from "commander";
2039
2684
  import inquirer4 from "inquirer";
2040
- import path6 from "path";
2685
+ import path8 from "path";
2041
2686
  import { Listr as Listr3 } from "listr2";
2042
2687
  async function addFeatureFromPrd(featureName, featureSlug, specFile) {
2043
2688
  const { prdPath } = await inquirer4.prompt([
@@ -2066,7 +2711,7 @@ async function addFeatureFromPrd(featureName, featureSlug, specFile) {
2066
2711
  const specs = files.filter((f) => !f.includes("template"));
2067
2712
  ctx2.completedSpecs = [];
2068
2713
  for (const file of specs) {
2069
- const status = await SpecUtils.getSpecStatus(path6.join(specDir, file));
2714
+ const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
2070
2715
  if (status === "\u5DF2\u5B8C\u6210") {
2071
2716
  ctx2.completedSpecs.push(file.replace(".md", ""));
2072
2717
  }
@@ -2138,7 +2783,7 @@ async function addFeatureSimple(featureName, featureSlug, specFile) {
2138
2783
  const specs = files.filter((f) => !f.includes("template"));
2139
2784
  ctx2.completedSpecs = [];
2140
2785
  for (const file of specs) {
2141
- const status = await SpecUtils.getSpecStatus(path6.join(specDir, file));
2786
+ const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
2142
2787
  if (status === "\u5DF2\u5B8C\u6210") {
2143
2788
  ctx2.completedSpecs.push(file.replace(".md", ""));
2144
2789
  }
@@ -2235,7 +2880,7 @@ async function buildProjectContext() {
2235
2880
  const files = await FileUtils.findFiles("*.md", "docs/specs");
2236
2881
  const specs = files.filter((f) => !f.includes("template"));
2237
2882
  for (const file of specs) {
2238
- const status = await SpecUtils.getSpecStatus(path6.join("docs/specs", file));
2883
+ const status = await SpecUtils.getSpecStatus(path8.join("docs/specs", file));
2239
2884
  context.push(` - ${file.replace(".md", "")} [${status}]`);
2240
2885
  }
2241
2886
  }
@@ -2498,7 +3143,7 @@ var init_add_feature = __esm({
2498
3143
  process.exit(1);
2499
3144
  }
2500
3145
  const featureSlug = StringUtils2.toKebabCase(featureName);
2501
- const specFile = path6.join("docs/specs", `${featureSlug}.md`);
3146
+ const specFile = path8.join("docs/specs", `${featureSlug}.md`);
2502
3147
  const specExists = await FileUtils.exists(specFile);
2503
3148
  if (specExists) {
2504
3149
  logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
@@ -2535,7 +3180,7 @@ var init_add_feature = __esm({
2535
3180
 
2536
3181
  // src/commands/split-prd.ts
2537
3182
  import { Command as Command5 } from "commander";
2538
- import path7 from "path";
3183
+ import path9 from "path";
2539
3184
  import { Listr as Listr4 } from "listr2";
2540
3185
  function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
2541
3186
  let prompt = `Role: Senior Product Manager and Technical Architect
@@ -2681,7 +3326,7 @@ var init_split_prd = __esm({
2681
3326
  ctx2.prdFiles = [];
2682
3327
  for (const ext of supportedExtensions) {
2683
3328
  const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
2684
- ctx2.prdFiles.push(...files.map((f) => path7.join(prdFolder, f)));
3329
+ ctx2.prdFiles.push(...files.map((f) => path9.join(prdFolder, f)));
2685
3330
  }
2686
3331
  if (ctx2.prdFiles.length === 0) {
2687
3332
  throw new Error(
@@ -2699,7 +3344,7 @@ var init_split_prd = __esm({
2699
3344
  {
2700
3345
  title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
2701
3346
  task: async (ctx2) => {
2702
- const screenshotDir = path7.join(prdFolder, "screenshots");
3347
+ const screenshotDir = path9.join(prdFolder, "screenshots");
2703
3348
  const dirExists = await FileUtils.exists(screenshotDir);
2704
3349
  if (!dirExists) {
2705
3350
  logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
@@ -2710,7 +3355,7 @@ var init_split_prd = __esm({
2710
3355
  ctx2.screenshots = [];
2711
3356
  for (const ext of imageExtensions) {
2712
3357
  const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
2713
- ctx2.screenshots.push(...files.map((f) => path7.join(screenshotDir, f)));
3358
+ ctx2.screenshots.push(...files.map((f) => path9.join(screenshotDir, f)));
2714
3359
  }
2715
3360
  logger.success(`\u627E\u5230 ${ctx2.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
2716
3361
  }
@@ -2721,8 +3366,8 @@ var init_split_prd = __esm({
2721
3366
  const entries = await FileUtils.findFiles("*/", prdFolder);
2722
3367
  ctx2.demoRepos = [];
2723
3368
  for (const entry of entries) {
2724
- const dirPath = path7.join(prdFolder, entry);
2725
- const gitDir = path7.join(dirPath, ".git");
3369
+ const dirPath = path9.join(prdFolder, entry);
3370
+ const gitDir = path9.join(dirPath, ".git");
2726
3371
  const hasGit = await FileUtils.exists(gitDir);
2727
3372
  if (hasGit) {
2728
3373
  ctx2.demoRepos.push(dirPath);
@@ -2786,7 +3431,7 @@ var init_split_prd = __esm({
2786
3431
  // src/commands/bugfix.ts
2787
3432
  import { Command as Command6 } from "commander";
2788
3433
  import inquirer5 from "inquirer";
2789
- import path8 from "path";
3434
+ import path10 from "path";
2790
3435
  import { Listr as Listr5 } from "listr2";
2791
3436
  function generateBugId() {
2792
3437
  const date = /* @__PURE__ */ new Date();
@@ -2813,7 +3458,7 @@ async function findRelatedSpec(description) {
2813
3458
  }
2814
3459
  const keywords = extractKeywords(description);
2815
3460
  for (const file of specs) {
2816
- const filePath = path8.join(specDir, file);
3461
+ const filePath = path10.join(specDir, file);
2817
3462
  const content = await FileUtils.read(filePath);
2818
3463
  for (const keyword of keywords) {
2819
3464
  if (content.toLowerCase().includes(keyword.toLowerCase())) {
@@ -2953,7 +3598,7 @@ var init_bugfix = __esm({
2953
3598
  const relatedSpec = await findRelatedSpec(answers.description);
2954
3599
  const bugfixDir = "docs/bugfixes";
2955
3600
  await FileUtils.ensureDir(bugfixDir);
2956
- const bugfixFile = path8.join(bugfixDir, `${timestamp}_${bugId}.md`);
3601
+ const bugfixFile = path10.join(bugfixDir, `${timestamp}_${bugId}.md`);
2957
3602
  const content = formatBugfixDocument({
2958
3603
  id: bugId,
2959
3604
  severity: answers.severity,
@@ -3019,8 +3664,8 @@ var init_bugfix = __esm({
3019
3664
  {
3020
3665
  title: "\u521B\u5EFA hotfix \u5206\u652F",
3021
3666
  task: async () => {
3022
- const { execa: execa3 } = await import("execa");
3023
- await execa3("git", ["checkout", "-b", branchName], { stdio: "inherit" });
3667
+ const { execa: execa5 } = await import("execa");
3668
+ await execa5("git", ["checkout", "-b", branchName], { stdio: "inherit" });
3024
3669
  }
3025
3670
  },
3026
3671
  {
@@ -3029,7 +3674,7 @@ var init_bugfix = __esm({
3029
3674
  const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
3030
3675
  const hotfixDir = "docs/hotfixes";
3031
3676
  await FileUtils.ensureDir(hotfixDir);
3032
- const hotfixFile = path8.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
3677
+ const hotfixFile = path10.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
3033
3678
  const content = formatHotfixDocument({
3034
3679
  id: hotfixId,
3035
3680
  description: answers.description,
@@ -3044,9 +3689,9 @@ var init_bugfix = __esm({
3044
3689
  {
3045
3690
  title: "\u521B\u5EFA\u521D\u59CB commit",
3046
3691
  task: async () => {
3047
- const { execa: execa3 } = await import("execa");
3048
- await execa3("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
3049
- await execa3(
3692
+ const { execa: execa5 } = await import("execa");
3693
+ await execa5("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
3694
+ await execa5(
3050
3695
  "git",
3051
3696
  [
3052
3697
  "commit",
@@ -3084,7 +3729,7 @@ Temporary solution: ${answers.solution}`
3084
3729
 
3085
3730
  // src/commands/lint.ts
3086
3731
  import { Command as Command7 } from "commander";
3087
- import { execa as execa2 } from "execa";
3732
+ import { execa as execa3 } from "execa";
3088
3733
  var lintCommand;
3089
3734
  var init_lint = __esm({
3090
3735
  "src/commands/lint.ts"() {
@@ -3112,17 +3757,17 @@ var init_lint = __esm({
3112
3757
  try {
3113
3758
  logger.step("\u68C0\u67E5\u524D\u7AEF\u4EE3\u7801...");
3114
3759
  if (options.fix) {
3115
- await execa2("npm", ["run", "lint", "--", "--fix"], {
3760
+ await execa3("npm", ["run", "lint", "--", "--fix"], {
3116
3761
  cwd: "frontend",
3117
3762
  stdio: "inherit"
3118
3763
  });
3119
3764
  } else {
3120
- await execa2("npm", ["run", "lint"], {
3765
+ await execa3("npm", ["run", "lint"], {
3121
3766
  cwd: "frontend",
3122
3767
  stdio: "inherit"
3123
3768
  });
3124
3769
  }
3125
- await execa2("npx", ["tsc", "--noEmit"], {
3770
+ await execa3("npx", ["tsc", "--noEmit"], {
3126
3771
  cwd: "frontend",
3127
3772
  stdio: "pipe"
3128
3773
  });
@@ -3145,23 +3790,23 @@ var init_lint = __esm({
3145
3790
  logger.step("\u68C0\u67E5\u540E\u7AEF\u4EE3\u7801...");
3146
3791
  if (hasGradle) {
3147
3792
  if (options.fix) {
3148
- await execa2("./gradlew", ["spotlessApply"], {
3793
+ await execa3("./gradlew", ["spotlessApply"], {
3149
3794
  cwd: "backend",
3150
3795
  stdio: "inherit"
3151
3796
  });
3152
3797
  }
3153
- await execa2("./gradlew", ["checkstyleMain", "compileJava"], {
3798
+ await execa3("./gradlew", ["checkstyleMain", "compileJava"], {
3154
3799
  cwd: "backend",
3155
3800
  stdio: "inherit"
3156
3801
  });
3157
3802
  } else if (hasMaven) {
3158
3803
  if (options.fix) {
3159
- await execa2("./mvnw", ["spotless:apply"], {
3804
+ await execa3("./mvnw", ["spotless:apply"], {
3160
3805
  cwd: "backend",
3161
3806
  stdio: "inherit"
3162
3807
  });
3163
3808
  }
3164
- await execa2("./mvnw", ["checkstyle:check", "compile"], {
3809
+ await execa3("./mvnw", ["checkstyle:check", "compile"], {
3165
3810
  cwd: "backend",
3166
3811
  stdio: "inherit"
3167
3812
  });
@@ -3216,7 +3861,7 @@ var init_lint = __esm({
3216
3861
 
3217
3862
  // src/commands/status.ts
3218
3863
  import { Command as Command8 } from "commander";
3219
- import path9 from "path";
3864
+ import path11 from "path";
3220
3865
  async function displayProjectInfo() {
3221
3866
  logger.info("\u9879\u76EE\u4FE1\u606F:");
3222
3867
  logger.newLine();
@@ -3252,7 +3897,7 @@ async function displayFeatureInventory() {
3252
3897
  }
3253
3898
  const inventory = [];
3254
3899
  for (const file of specs) {
3255
- const filePath = path9.join(specDir, file);
3900
+ const filePath = path11.join(specDir, file);
3256
3901
  const content = await FileUtils.read(filePath);
3257
3902
  const status = parseSpecStatus2(content);
3258
3903
  inventory.push({
@@ -3311,7 +3956,7 @@ async function displayRecentActivity() {
3311
3956
  }
3312
3957
  const sorted = files.sort().reverse().slice(0, 5);
3313
3958
  for (const file of sorted) {
3314
- const filePath = path9.join(sessionDir, file);
3959
+ const filePath = path11.join(sessionDir, file);
3315
3960
  const stat = await FileUtils.read(filePath);
3316
3961
  const specMatch = stat.match(/\*\*Spec\*\*:\s*(.+)/);
3317
3962
  const spec = specMatch ? specMatch[1].trim() : "\u672A\u77E5";
@@ -3381,13 +4026,13 @@ var init_status = __esm({
3381
4026
 
3382
4027
  // src/commands/detect-deps.ts
3383
4028
  import { Command as Command9 } from "commander";
3384
- import path10 from "path";
4029
+ import path12 from "path";
3385
4030
  import inquirer6 from "inquirer";
3386
4031
  async function detectDependencies(specFile) {
3387
4032
  logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
3388
4033
  const projectDir = ".";
3389
4034
  const allDeps = /* @__PURE__ */ new Set();
3390
- const backendDir = path10.join(projectDir, "backend");
4035
+ const backendDir = path12.join(projectDir, "backend");
3391
4036
  const backendExists = await FileUtils.exists(backendDir);
3392
4037
  if (backendExists) {
3393
4038
  const apiDeps = await scanBackendApiCalls(backendDir);
@@ -3397,7 +4042,7 @@ async function detectDependencies(specFile) {
3397
4042
  const serviceDeps = await scanBackendServiceRefs(backendDir);
3398
4043
  serviceDeps.forEach((d) => allDeps.add(d));
3399
4044
  }
3400
- const frontendDir = path10.join(projectDir, "frontend");
4045
+ const frontendDir = path12.join(projectDir, "frontend");
3401
4046
  const frontendExists = await FileUtils.exists(frontendDir);
3402
4047
  if (frontendExists) {
3403
4048
  const frontendDeps = await scanFrontendApiCalls(frontendDir);
@@ -3438,11 +4083,11 @@ async function detectDependencies(specFile) {
3438
4083
  }
3439
4084
  async function scanBackendApiCalls(backendDir) {
3440
4085
  const deps = [];
3441
- const srcDir = path10.join(backendDir, "src");
4086
+ const srcDir = path12.join(backendDir, "src");
3442
4087
  try {
3443
4088
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3444
4089
  for (const file of javaFiles) {
3445
- const filePath = path10.join(srcDir, file);
4090
+ const filePath = path12.join(srcDir, file);
3446
4091
  const content = await FileUtils.read(filePath);
3447
4092
  const pathRegex = /"(\/api\/[^"]+)"/g;
3448
4093
  let match;
@@ -3460,11 +4105,11 @@ async function scanBackendApiCalls(backendDir) {
3460
4105
  }
3461
4106
  async function scanBackendEntityRelations(backendDir) {
3462
4107
  const deps = [];
3463
- const srcDir = path10.join(backendDir, "src");
4108
+ const srcDir = path12.join(backendDir, "src");
3464
4109
  try {
3465
4110
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3466
4111
  for (const file of javaFiles) {
3467
- const filePath = path10.join(srcDir, file);
4112
+ const filePath = path12.join(srcDir, file);
3468
4113
  const content = await FileUtils.read(filePath);
3469
4114
  if (content.includes("@JoinColumn") || content.includes("@ManyToOne") || content.includes("@OneToMany")) {
3470
4115
  const typeRegex = /type\s*=\s*(\w+)/g;
@@ -3480,11 +4125,11 @@ async function scanBackendEntityRelations(backendDir) {
3480
4125
  }
3481
4126
  async function scanBackendServiceRefs(backendDir) {
3482
4127
  const deps = [];
3483
- const srcDir = path10.join(backendDir, "src");
4128
+ const srcDir = path12.join(backendDir, "src");
3484
4129
  try {
3485
4130
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3486
4131
  for (const file of javaFiles) {
3487
- const filePath = path10.join(srcDir, file);
4132
+ const filePath = path12.join(srcDir, file);
3488
4133
  const content = await FileUtils.read(filePath);
3489
4134
  const serviceRegex = /private\s+(\w+)Service/g;
3490
4135
  let match;
@@ -3500,11 +4145,11 @@ async function scanBackendServiceRefs(backendDir) {
3500
4145
  }
3501
4146
  async function scanFrontendApiCalls(frontendDir) {
3502
4147
  const deps = [];
3503
- const srcDir = path10.join(frontendDir, "src");
4148
+ const srcDir = path12.join(frontendDir, "src");
3504
4149
  try {
3505
4150
  const tsFiles = await FileUtils.findFiles("*.{ts,tsx,js,jsx}", srcDir);
3506
4151
  for (const file of tsFiles) {
3507
- const filePath = path10.join(srcDir, file);
4152
+ const filePath = path12.join(srcDir, file);
3508
4153
  const content = await FileUtils.read(filePath);
3509
4154
  const pathRegex = /"(\/api\/[^"]+)"/g;
3510
4155
  let match;
@@ -3621,7 +4266,7 @@ var init_detect_deps = __esm({
3621
4266
  process.exit(1);
3622
4267
  }
3623
4268
  for (const spec of specs) {
3624
- const specPath = path10.join(specsDir, spec);
4269
+ const specPath = path12.join(specsDir, spec);
3625
4270
  logger.step(`\u5904\u7406: ${spec}`);
3626
4271
  await detectDependencies(specPath);
3627
4272
  logger.newLine();
@@ -3648,10 +4293,10 @@ var init_detect_deps = __esm({
3648
4293
 
3649
4294
  // src/commands/sync-memory.ts
3650
4295
  import { Command as Command10 } from "commander";
3651
- import path11 from "path";
4296
+ import path13 from "path";
3652
4297
  async function syncFeatureInventory(aiMemoryFile, projectDir) {
3653
4298
  logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
3654
- const specsDir = path11.join(projectDir, "docs/specs");
4299
+ const specsDir = path13.join(projectDir, "docs/specs");
3655
4300
  const exists = await FileUtils.exists(specsDir);
3656
4301
  if (!exists) {
3657
4302
  return;
@@ -3666,7 +4311,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
3666
4311
  for (const specFile of specs) {
3667
4312
  const name = specFile.replace(".md", "");
3668
4313
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3669
- const specPath = path11.join(specsDir, specFile);
4314
+ const specPath = path13.join(specsDir, specFile);
3670
4315
  const content = await FileUtils.read(specPath);
3671
4316
  const status = parseSpecStatus3(content);
3672
4317
  const progress = getSpecProgress(content);
@@ -3684,7 +4329,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
3684
4329
  }
3685
4330
  async function syncApiInventory(aiMemoryFile, projectDir) {
3686
4331
  logger.step("\u540C\u6B65 API \u5217\u8868...");
3687
- const backendDir = path11.join(projectDir, "backend");
4332
+ const backendDir = path13.join(projectDir, "backend");
3688
4333
  const exists = await FileUtils.exists(backendDir);
3689
4334
  if (!exists) {
3690
4335
  return;
@@ -3694,13 +4339,13 @@ async function syncApiInventory(aiMemoryFile, projectDir) {
3694
4339
  lines.push("");
3695
4340
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210");
3696
4341
  lines.push("");
3697
- const srcDir = path11.join(backendDir, "src");
4342
+ const srcDir = path13.join(backendDir, "src");
3698
4343
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3699
4344
  if (controllers.length === 0) {
3700
4345
  lines.push("\u6682\u65E0 API");
3701
4346
  } else {
3702
4347
  for (const controllerFile of controllers) {
3703
- const controllerPath = path11.join(srcDir, controllerFile);
4348
+ const controllerPath = path13.join(srcDir, controllerFile);
3704
4349
  const controllerName = controllerFile.replace(".java", "");
3705
4350
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
3706
4351
  lines.push(`### ${module} \u6A21\u5757`);
@@ -3777,7 +4422,7 @@ function extractMethodComment(content, methodName) {
3777
4422
  }
3778
4423
  async function syncDataModels(aiMemoryFile, projectDir) {
3779
4424
  logger.step("\u540C\u6B65\u6570\u636E\u6A21\u578B...");
3780
- const backendDir = path11.join(projectDir, "backend");
4425
+ const backendDir = path13.join(projectDir, "backend");
3781
4426
  const exists = await FileUtils.exists(backendDir);
3782
4427
  if (!exists) {
3783
4428
  return;
@@ -3787,7 +4432,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
3787
4432
  lines.push("");
3788
4433
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Entity \u751F\u6210");
3789
4434
  lines.push("");
3790
- const srcDir = path11.join(backendDir, "src");
4435
+ const srcDir = path13.join(backendDir, "src");
3791
4436
  const entities = await FileUtils.findFiles("*Entity.java", srcDir);
3792
4437
  if (entities.length === 0) {
3793
4438
  lines.push("\u6682\u65E0\u6570\u636E\u6A21\u578B");
@@ -3795,7 +4440,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
3795
4440
  lines.push("| \u6A21\u578B | \u8BF4\u660E | \u5B57\u6BB5 | \u5173\u8054 |");
3796
4441
  lines.push("|------|------|------|------|");
3797
4442
  for (const entityFile of entities) {
3798
- const entityPath = path11.join(srcDir, entityFile);
4443
+ const entityPath = path13.join(srcDir, entityFile);
3799
4444
  const entityName = entityFile.replace(".java", "").replace(/Entity$/, "");
3800
4445
  const displayName = entityName.split(/(?=[A-Z])/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
3801
4446
  const content = await FileUtils.read(entityPath);
@@ -3884,6 +4529,49 @@ function getSpecProgress(spec) {
3884
4529
  }
3885
4530
  return `${completedTodos}/${totalTodos}`;
3886
4531
  }
4532
+ async function syncTemplateVersions(aiMemoryFile, projectDir) {
4533
+ logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
4534
+ const config = await readTemplateConfig(projectDir);
4535
+ if (!config) {
4536
+ return;
4537
+ }
4538
+ const lines = [];
4539
+ lines.push("## \u6A21\u677F\u7248\u672C\u4FE1\u606F");
4540
+ lines.push("");
4541
+ 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");
4542
+ lines.push("");
4543
+ lines.push("| \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |");
4544
+ lines.push("|------|------|-----|--------|--------|----------|");
4545
+ if (config.frontend) {
4546
+ const repoName = extractRepoName(config.frontend.repository);
4547
+ const commit = config.frontend.commit?.substring(0, 8) || "-";
4548
+ const tag = config.frontend.tag || "-";
4549
+ const branch = config.frontend.branch || "-";
4550
+ const updateDate = config.frontend.lastUpdate ? new Date(config.frontend.lastUpdate).toLocaleDateString("zh-CN") : "-";
4551
+ lines.push(`| \u524D\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
4552
+ }
4553
+ if (config.backend) {
4554
+ const repoName = extractRepoName(config.backend.repository);
4555
+ const commit = config.backend.commit?.substring(0, 8) || "-";
4556
+ const tag = config.backend.tag || "-";
4557
+ const branch = config.backend.branch || "-";
4558
+ const updateDate = config.backend.lastUpdate ? new Date(config.backend.lastUpdate).toLocaleDateString("zh-CN") : "-";
4559
+ lines.push(`| \u540E\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
4560
+ }
4561
+ const newContent = lines.join("\n") + "\n";
4562
+ await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
4563
+ }
4564
+ function extractRepoName(repository) {
4565
+ let path17 = repository;
4566
+ path17 = path17.replace(/^https?:\/\//, "");
4567
+ path17 = path17.replace(/^git@/, "");
4568
+ const parts = path17.split("/");
4569
+ if (parts.length > 1) {
4570
+ path17 = parts.slice(1).join("/");
4571
+ }
4572
+ path17 = path17.replace(/\.git$/, "");
4573
+ return path17;
4574
+ }
3887
4575
  var syncMemoryCommand;
3888
4576
  var init_sync_memory = __esm({
3889
4577
  "src/commands/sync-memory.ts"() {
@@ -3891,6 +4579,7 @@ var init_sync_memory = __esm({
3891
4579
  init_esm_shims();
3892
4580
  init_utils();
3893
4581
  init_logger();
4582
+ init_template_version();
3894
4583
  syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
3895
4584
  try {
3896
4585
  logger.header("\u540C\u6B65 AI_MEMORY.md");
@@ -3910,6 +4599,7 @@ var init_sync_memory = __esm({
3910
4599
  await syncFeatureInventory(aiMemoryFile, ".");
3911
4600
  await syncApiInventory(aiMemoryFile, ".");
3912
4601
  await syncDataModels(aiMemoryFile, ".");
4602
+ await syncTemplateVersions(aiMemoryFile, ".");
3913
4603
  await updateSyncTime(aiMemoryFile);
3914
4604
  logger.success("AI_MEMORY.md \u5DF2\u540C\u6B65");
3915
4605
  } catch (error) {
@@ -3925,11 +4615,11 @@ var init_sync_memory = __esm({
3925
4615
 
3926
4616
  // src/commands/check-api.ts
3927
4617
  import { Command as Command11 } from "commander";
3928
- import path12 from "path";
4618
+ import path14 from "path";
3929
4619
  import inquirer7 from "inquirer";
3930
4620
  import { Listr as Listr6 } from "listr2";
3931
4621
  async function checkApiConflicts(projectDir) {
3932
- const backendDir = path12.join(projectDir, "backend");
4622
+ const backendDir = path14.join(projectDir, "backend");
3933
4623
  const exists = await FileUtils.exists(backendDir);
3934
4624
  if (!exists) {
3935
4625
  logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE");
@@ -3938,10 +4628,10 @@ async function checkApiConflicts(projectDir) {
3938
4628
  logger.step("\u626B\u63CF\u540E\u7AEF API...");
3939
4629
  logger.newLine();
3940
4630
  const apiMap = /* @__PURE__ */ new Map();
3941
- const srcDir = path12.join(backendDir, "src");
4631
+ const srcDir = path14.join(backendDir, "src");
3942
4632
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3943
4633
  for (const controllerFile of controllers) {
3944
- const controllerPath = path12.join(srcDir, controllerFile);
4634
+ const controllerPath = path14.join(srcDir, controllerFile);
3945
4635
  const apis = await extractApisFromController(controllerPath);
3946
4636
  for (const api of apis) {
3947
4637
  const key = `${api.method}:${api.path}`;
@@ -3972,8 +4662,8 @@ async function checkApiConflicts(projectDir) {
3972
4662
  }
3973
4663
  }
3974
4664
  async function detectApiChanges(projectDir) {
3975
- const backendDir = path12.join(projectDir, "backend");
3976
- const registryFile = path12.join(projectDir, "docs/api-registry.md");
4665
+ const backendDir = path14.join(projectDir, "backend");
4666
+ const registryFile = path14.join(projectDir, "docs/api-registry.md");
3977
4667
  const registryExists = await FileUtils.exists(registryFile);
3978
4668
  if (!registryExists) {
3979
4669
  logger.info("API Registry \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u53D8\u66F4\u68C0\u6D4B");
@@ -3985,10 +4675,10 @@ async function detectApiChanges(projectDir) {
3985
4675
  const registryContent = await FileUtils.read(registryFile);
3986
4676
  const existingApis = extractApisFromRegistry(registryContent);
3987
4677
  const currentApis = /* @__PURE__ */ new Map();
3988
- const srcDir = path12.join(backendDir, "src");
4678
+ const srcDir = path14.join(backendDir, "src");
3989
4679
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3990
4680
  for (const controllerFile of controllers) {
3991
- const controllerPath = path12.join(srcDir, controllerFile);
4681
+ const controllerPath = path14.join(srcDir, controllerFile);
3992
4682
  const apis = await extractApisFromController(controllerPath);
3993
4683
  for (const api of apis) {
3994
4684
  const key = `${api.method}:${api.path}`;
@@ -4050,9 +4740,9 @@ async function detectApiChanges(projectDir) {
4050
4740
  }
4051
4741
  }
4052
4742
  async function generateApiRegistry(projectDir) {
4053
- const registryFile = path12.join(projectDir, "docs/api-registry.md");
4743
+ const registryFile = path14.join(projectDir, "docs/api-registry.md");
4054
4744
  logger.step("\u626B\u63CF\u5E76\u751F\u6210 API Registry...");
4055
- await FileUtils.ensureDir(path12.dirname(registryFile));
4745
+ await FileUtils.ensureDir(path14.dirname(registryFile));
4056
4746
  const header = `# API Registry
4057
4747
 
4058
4748
  > \u672C\u6587\u4EF6\u8BB0\u5F55\u6240\u6709 API \u7684\u5B9A\u4E49\u3001\u7248\u672C\u548C\u53D8\u66F4\u5386\u53F2
@@ -4081,14 +4771,14 @@ async function generateApiRegistry(projectDir) {
4081
4771
  *\u6700\u540E\u66F4\u65B0: ${DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss")}*
4082
4772
  `;
4083
4773
  let content = header;
4084
- const backendDir = path12.join(projectDir, "backend");
4774
+ const backendDir = path14.join(projectDir, "backend");
4085
4775
  const exists = await FileUtils.exists(backendDir);
4086
4776
  if (exists) {
4087
- const srcDir = path12.join(backendDir, "src");
4777
+ const srcDir = path14.join(backendDir, "src");
4088
4778
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
4089
4779
  const moduleMap = /* @__PURE__ */ new Map();
4090
4780
  for (const controllerFile of controllers) {
4091
- const controllerPath = path12.join(srcDir, controllerFile);
4781
+ const controllerPath = path14.join(srcDir, controllerFile);
4092
4782
  const controllerName = controllerFile.replace(".java", "");
4093
4783
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
4094
4784
  if (!moduleMap.has(module)) {
@@ -4172,10 +4862,10 @@ function extractApisFromRegistry(registryContent) {
4172
4862
  let match;
4173
4863
  while ((match = apiRegex.exec(registryContent)) !== null) {
4174
4864
  const method = match[1];
4175
- const path14 = match[2].trim();
4865
+ const path17 = match[2].trim();
4176
4866
  const description = match[3].trim();
4177
- const key = `${method}:${path14}`;
4178
- apis.set(key, { method, path: path14, description });
4867
+ const key = `${method}:${path17}`;
4868
+ apis.set(key, { method, path: path17, description });
4179
4869
  }
4180
4870
  return apis;
4181
4871
  }
@@ -4263,7 +4953,7 @@ var init_check_api = __esm({
4263
4953
 
4264
4954
  // src/commands/logs.ts
4265
4955
  import { Command as Command12 } from "commander";
4266
- import path13 from "path";
4956
+ import path15 from "path";
4267
4957
  import inquirer8 from "inquirer";
4268
4958
  async function collectLogFiles(targetDir) {
4269
4959
  const logs = [];
@@ -4271,7 +4961,7 @@ async function collectLogFiles(targetDir) {
4271
4961
  const allFiles = await FileUtils.findFiles("*.md", targetDir);
4272
4962
  const filtered = allFiles.filter((f) => f !== "index.md");
4273
4963
  for (const file of filtered) {
4274
- const filePath = path13.join(targetDir, file);
4964
+ const filePath = path15.join(targetDir, file);
4275
4965
  const stat = await FileUtils.exists(filePath);
4276
4966
  if (stat) {
4277
4967
  logs.push(filePath);
@@ -4311,7 +5001,7 @@ var init_logs = __esm({
4311
5001
  case "":
4312
5002
  case "today": {
4313
5003
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4314
- targetDir = path13.join(sessionsDir, today);
5004
+ targetDir = path15.join(sessionsDir, today);
4315
5005
  const todayExists = await FileUtils.exists(targetDir);
4316
5006
  if (!todayExists) {
4317
5007
  logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
@@ -4327,7 +5017,7 @@ var init_logs = __esm({
4327
5017
  break;
4328
5018
  }
4329
5019
  default: {
4330
- targetDir = path13.join(sessionsDir, filter);
5020
+ targetDir = path15.join(sessionsDir, filter);
4331
5021
  const dateExists = await FileUtils.exists(targetDir);
4332
5022
  if (!dateExists) {
4333
5023
  logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
@@ -4351,7 +5041,7 @@ var init_logs = __esm({
4351
5041
  process.exit(0);
4352
5042
  }
4353
5043
  for (let i = 0; i < logs.length; i++) {
4354
- const relPath = path13.relative(sessionsDir, logs[i]);
5044
+ const relPath = path15.relative(sessionsDir, logs[i]);
4355
5045
  logger.step(`${i + 1}) ${relPath}`);
4356
5046
  }
4357
5047
  logger.newLine();
@@ -4389,15 +5079,709 @@ var init_logs = __esm({
4389
5079
  }
4390
5080
  });
4391
5081
 
4392
- // src/index.ts
4393
- var index_exports = {};
5082
+ // src/commands/update.ts
4394
5083
  import { Command as Command13 } from "commander";
5084
+ import path16 from "path";
5085
+ import { execa as execa4 } from "execa";
5086
+ import inquirer9 from "inquirer";
5087
+ import fs3 from "fs-extra";
5088
+ async function performUpdate(projectPath, updates) {
5089
+ logger.newLine();
5090
+ logger.info("\u5F00\u59CB\u66F4\u65B0\u6A21\u677F...");
5091
+ for (const update of updates) {
5092
+ const { type, info, updateOptions } = update;
5093
+ const targetDir = type === "frontend" ? "frontend" : "backend";
5094
+ const targetPath = path16.join(projectPath, targetDir);
5095
+ logger.newLine();
5096
+ logger.step(`\u66F4\u65B0 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
5097
+ if (updateOptions?.tag || updateOptions?.branch) {
5098
+ const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
5099
+ const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
5100
+ const config = await userConfigManager2.getGitLabConfig();
5101
+ if (config) {
5102
+ const gitlabAPI = new GitLabAPI2(config);
5103
+ const projectPathEncoded = GitLabAPI2.parseProjectPath(info.repository);
5104
+ if (updateOptions.tag) {
5105
+ const isValid = await gitlabAPI.validateTag(projectPathEncoded, updateOptions.tag);
5106
+ if (!isValid) {
5107
+ logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag "${updateOptions.tag}" \u4E0D\u5B58\u5728`);
5108
+ continue;
5109
+ }
5110
+ logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag: ${updateOptions.tag}`);
5111
+ }
5112
+ if (updateOptions.branch) {
5113
+ const isValid = await gitlabAPI.validateBranch(projectPathEncoded, updateOptions.branch);
5114
+ if (!isValid) {
5115
+ logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F "${updateOptions.branch}" \u4E0D\u5B58\u5728`);
5116
+ continue;
5117
+ }
5118
+ logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F: ${updateOptions.branch}`);
5119
+ }
5120
+ } else {
5121
+ logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
5122
+ }
5123
+ }
5124
+ const ref = updateOptions?.tag || updateOptions?.branch || "HEAD";
5125
+ const backupDir = path16.join(projectPath, `.backup-${Date.now()}`);
5126
+ await fs3.copy(targetPath, path16.join(backupDir, targetDir));
5127
+ logger.info(`\u5DF2\u521B\u5EFA\u5907\u4EFD: ${backupDir}`);
5128
+ if (updateOptions?.dryRun) {
5129
+ logger.info("[Dry Run] \u5C06\u4F1A\u66F4\u65B0\u5230\u4EE5\u4E0B\u7248\u672C:");
5130
+ logger.info(` Ref: ${ref}`);
5131
+ logger.info(` \u4ED3\u5E93: ${info.repository}`);
5132
+ logger.info("\u5907\u4EFD\u5DF2\u521B\u5EFA\uFF0C\u8DF3\u8FC7\u5B9E\u9645\u66F4\u65B0");
5133
+ continue;
5134
+ }
5135
+ try {
5136
+ const tempDir = path16.join(projectPath, `.template-update-${Date.now()}`);
5137
+ await execa4("git", ["clone", "--depth=1", "--branch", ref, info.repository, tempDir], {
5138
+ stdio: "pipe"
5139
+ });
5140
+ const { stdout: commit } = await execa4("git", ["rev-parse", "HEAD"], {
5141
+ cwd: tempDir,
5142
+ stdio: "pipe"
5143
+ });
5144
+ const { stdout: tags } = await execa4("git", ["tag", "-l", "--sort=-v:refname"], {
5145
+ cwd: tempDir,
5146
+ stdio: "pipe"
5147
+ });
5148
+ const latestTag = tags.split("\n")[0] || void 0;
5149
+ const keepFiles = [".env.local", "package-lock.json", "node_modules"];
5150
+ const currentFiles = await FileUtils.findFiles("*", targetPath);
5151
+ for (const file of currentFiles) {
5152
+ if (!keepFiles.includes(file)) {
5153
+ const filePath = path16.join(targetPath, file);
5154
+ try {
5155
+ await fs3.remove(filePath);
5156
+ } catch {
5157
+ }
5158
+ }
5159
+ }
5160
+ await fs3.copy(tempDir, targetPath, {
5161
+ filter: (src) => !src.includes(".git")
5162
+ });
5163
+ await fs3.remove(tempDir);
5164
+ await updateTemplateVersion(projectPath, type, commit.trim(), {
5165
+ tag: updateOptions?.tag || latestTag,
5166
+ branch: updateOptions?.branch
5167
+ });
5168
+ logger.success(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!`);
5169
+ logger.info(`\u65B0\u7248\u672C: ${updateOptions?.tag || updateOptions?.branch || latestTag || commit.substring(0, 8)}`);
5170
+ logger.info(`\u5907\u4EFD\u4F4D\u7F6E: ${backupDir}`);
5171
+ } catch (error) {
5172
+ logger.error(`\u66F4\u65B0\u5931\u8D25: ${error.message}`);
5173
+ logger.info("\u6B63\u5728\u6062\u590D\u5907\u4EFD...");
5174
+ await fs3.remove(targetPath);
5175
+ await fs3.copy(path16.join(backupDir, targetDir), targetPath);
5176
+ await fs3.remove(backupDir);
5177
+ logger.info("\u5DF2\u6062\u590D\u5230\u66F4\u65B0\u524D\u7684\u72B6\u6001");
5178
+ }
5179
+ }
5180
+ logger.newLine();
5181
+ logger.header("\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!");
5182
+ logger.newLine();
5183
+ logger.info("\u4E0B\u4E00\u6B65:");
5184
+ logger.step("1. \u68C0\u67E5\u66F4\u65B0\u540E\u7684\u4EE3\u7801");
5185
+ logger.step("2. \u8FD0\u884C npm install \u5B89\u88C5\u65B0\u4F9D\u8D56");
5186
+ logger.step("3. \u6D4B\u8BD5\u5E94\u7528\u662F\u5426\u6B63\u5E38\u8FD0\u884C");
5187
+ logger.step("4. \u63D0\u4EA4\u4EE3\u7801\u5230 Git");
5188
+ logger.newLine();
5189
+ }
5190
+ var updateCommand;
5191
+ var init_update = __esm({
5192
+ "src/commands/update.ts"() {
5193
+ "use strict";
5194
+ init_esm_shims();
5195
+ init_template_version();
5196
+ init_logger();
5197
+ init_utils();
5198
+ 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) => {
5199
+ try {
5200
+ logger.header("\u6A21\u677F\u7248\u672C\u68C0\u67E5");
5201
+ logger.newLine();
5202
+ const hasConfig = await FileUtils.exists("TECH_STACK.md");
5203
+ if (!hasConfig) {
5204
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
5205
+ logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
5206
+ process.exit(1);
5207
+ }
5208
+ const projectPath = ".";
5209
+ const checkAll = !options.frontend && !options.backend;
5210
+ const checkFrontend = options.frontend || checkAll;
5211
+ const checkBackend = options.backend || checkAll;
5212
+ const forceUpdate = options.tag || options.branch;
5213
+ const updates = [];
5214
+ const updateOptions = {
5215
+ tag: options.tag,
5216
+ branch: options.branch,
5217
+ dryRun: options.dryRun
5218
+ };
5219
+ if (checkFrontend) {
5220
+ logger.step("\u68C0\u67E5\u524D\u7AEF\u6A21\u677F...");
5221
+ const frontendInfo = await checkTemplateUpdate(projectPath, "frontend");
5222
+ if (frontendInfo) {
5223
+ if (frontendInfo.needsUpdate || forceUpdate) {
5224
+ const version = updateOptions.tag || updateOptions.branch || frontendInfo.latestTag || frontendInfo.latestCommit?.substring(0, 8);
5225
+ logger.success(`\u524D\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
5226
+ updates.push({ type: "frontend", info: frontendInfo, updateOptions });
5227
+ } else {
5228
+ logger.info("\u524D\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
5229
+ }
5230
+ } else {
5231
+ logger.info("\u524D\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
5232
+ }
5233
+ logger.newLine();
5234
+ }
5235
+ if (checkBackend) {
5236
+ logger.step("\u68C0\u67E5\u540E\u7AEF\u6A21\u677F...");
5237
+ const backendInfo = await checkTemplateUpdate(projectPath, "backend");
5238
+ if (backendInfo) {
5239
+ if (backendInfo.needsUpdate || forceUpdate) {
5240
+ const version = updateOptions.tag || updateOptions.branch || backendInfo.latestTag || backendInfo.latestCommit?.substring(0, 8);
5241
+ logger.success(`\u540E\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
5242
+ updates.push({ type: "backend", info: backendInfo, updateOptions });
5243
+ } else {
5244
+ logger.info("\u540E\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
5245
+ }
5246
+ } else {
5247
+ logger.info("\u540E\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
5248
+ }
5249
+ logger.newLine();
5250
+ }
5251
+ if (updates.length === 0) {
5252
+ logger.success("\u6240\u6709\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C!");
5253
+ return;
5254
+ }
5255
+ if (options.dryRun) {
5256
+ logger.header(`[Dry Run] \u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F`);
5257
+ } else {
5258
+ logger.header(`\u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F\u66F4\u65B0`);
5259
+ }
5260
+ logger.newLine();
5261
+ for (const update of updates) {
5262
+ const version = update.updateOptions?.tag || update.updateOptions?.branch || update.info.latestTag || update.info.latestCommit?.substring(0, 8);
5263
+ logger.step(`${update.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${version}`);
5264
+ }
5265
+ logger.newLine();
5266
+ if (options.dryRun) {
5267
+ logger.info("Dry run \u6A21\u5F0F\uFF0C\u4E0D\u6267\u884C\u5B9E\u9645\u66F4\u65B0");
5268
+ return;
5269
+ }
5270
+ const answers = await inquirer9.prompt([
5271
+ {
5272
+ type: "confirm",
5273
+ name: "shouldUpdate",
5274
+ message: "\u662F\u5426\u66F4\u65B0\u6A21\u677F?",
5275
+ default: false
5276
+ }
5277
+ ]);
5278
+ if (answers.shouldUpdate) {
5279
+ await performUpdate(projectPath, updates);
5280
+ } else {
5281
+ logger.info("\u5DF2\u53D6\u6D88\u66F4\u65B0");
5282
+ }
5283
+ } catch (error) {
5284
+ logger.error(`\u66F4\u65B0\u68C0\u67E5\u5931\u8D25: ${error.message}`);
5285
+ if (process.env.DEBUG) {
5286
+ console.error(error);
5287
+ }
5288
+ process.exit(1);
5289
+ }
5290
+ });
5291
+ }
5292
+ });
5293
+
5294
+ // src/commands/config.ts
5295
+ import { Command as Command14 } from "commander";
5296
+ import inquirer10 from "inquirer";
4395
5297
  import chalk2 from "chalk";
5298
+ var setTokenCommand, showConfigCommand, removeConfigCommand, validateTokenCommand, configCommand;
5299
+ var init_config = __esm({
5300
+ "src/commands/config.ts"() {
5301
+ "use strict";
5302
+ init_esm_shims();
5303
+ init_user_config();
5304
+ init_gitlab_api();
5305
+ init_logger();
5306
+ 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) => {
5307
+ try {
5308
+ logger.header("GitLab Access Token \u914D\u7F6E");
5309
+ logger.newLine();
5310
+ let { token, url } = options;
5311
+ if (!token) {
5312
+ const answers = await inquirer10.prompt([
5313
+ {
5314
+ type: "password",
5315
+ name: "token",
5316
+ message: "\u8BF7\u8F93\u5165 GitLab Access Token:",
5317
+ mask: "*",
5318
+ validate: (input) => {
5319
+ if (!input || input.trim().length === 0) {
5320
+ return "Token \u4E0D\u80FD\u4E3A\u7A7A";
5321
+ }
5322
+ return true;
5323
+ }
5324
+ },
5325
+ {
5326
+ type: "input",
5327
+ name: "url",
5328
+ message: "\u8BF7\u8F93\u5165 GitLab Base URL:",
5329
+ default: "https://gitlab.com",
5330
+ validate: (input) => {
5331
+ try {
5332
+ new URL(input);
5333
+ return true;
5334
+ } catch {
5335
+ return "\u8BF7\u8F93\u5165\u6709\u6548\u7684 URL";
5336
+ }
5337
+ }
5338
+ }
5339
+ ]);
5340
+ token = answers.token;
5341
+ url = answers.url;
5342
+ }
5343
+ try {
5344
+ new URL(url);
5345
+ } catch {
5346
+ logger.error("\u65E0\u6548\u7684 GitLab URL");
5347
+ process.exit(1);
5348
+ }
5349
+ url = url.replace(/\/+$/, "");
5350
+ logger.info("\u9A8C\u8BC1 Token...");
5351
+ const gitlabAPI = new GitLabAPI({ accessToken: token, baseUrl: url });
5352
+ const isValid = await gitlabAPI.authenticate();
5353
+ if (!isValid) {
5354
+ logger.error("Token \u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5:");
5355
+ logger.info("1. Token \u662F\u5426\u6B63\u786E");
5356
+ logger.info("2. Token \u662F\u5426\u6709 api \u6743\u9650");
5357
+ logger.info("3. GitLab URL \u662F\u5426\u6B63\u786E");
5358
+ process.exit(1);
5359
+ }
5360
+ logger.success("Token \u9A8C\u8BC1\u6210\u529F!");
5361
+ await userConfigManager.updateGitLabToken(token, url);
5362
+ logger.success("\u914D\u7F6E\u5DF2\u4FDD\u5B58!");
5363
+ logger.newLine();
5364
+ logger.info(`GitLab URL: ${chalk2.cyan(url)}`);
5365
+ logger.info(`\u914D\u7F6E\u6587\u4EF6: ${chalk2.gray(userConfigManager.getConfigPath())}`);
5366
+ } catch (error) {
5367
+ logger.error(`\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5368
+ if (process.env.DEBUG) {
5369
+ console.error(error);
5370
+ }
5371
+ process.exit(1);
5372
+ }
5373
+ });
5374
+ showConfigCommand = new Command14("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
5375
+ try {
5376
+ logger.header("GitLab \u914D\u7F6E");
5377
+ logger.newLine();
5378
+ const hasConfig = await userConfigManager.hasConfig();
5379
+ if (!hasConfig) {
5380
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5381
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5382
+ } else {
5383
+ const config = await userConfigManager.load();
5384
+ if (config?.gitlab) {
5385
+ const token = config.gitlab.accessToken;
5386
+ const maskedToken = token ? `${token.substring(0, 8)}${"*".repeat(Math.min(16, token.length - 8))}` : "";
5387
+ logger.info(`GitLab URL: ${chalk2.cyan(config.gitlab.baseUrl)}`);
5388
+ logger.info(`Access Token: ${chalk2.yellow(maskedToken)}`);
5389
+ logger.info(`Timeout: ${chalk2.gray(config.gitlab.timeout || 3e4)}ms`);
5390
+ if (config.preferences) {
5391
+ logger.newLine();
5392
+ logger.info("\u504F\u597D\u8BBE\u7F6E:");
5393
+ if (config.preferences.defaultBackendBranch) {
5394
+ logger.info(` \u9ED8\u8BA4\u540E\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultBackendBranch)}`);
5395
+ }
5396
+ if (config.preferences.defaultFrontendBranch) {
5397
+ logger.info(` \u9ED8\u8BA4\u524D\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultFrontendBranch)}`);
5398
+ }
5399
+ }
5400
+ logger.newLine();
5401
+ logger.info("\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E:");
5402
+ logger.info(` ${chalk2.gray(userConfigManager.getConfigPath())}`);
5403
+ }
5404
+ }
5405
+ } catch (error) {
5406
+ logger.error(`\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5407
+ if (process.env.DEBUG) {
5408
+ console.error(error);
5409
+ }
5410
+ process.exit(1);
5411
+ }
5412
+ });
5413
+ removeConfigCommand = new Command14("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
5414
+ try {
5415
+ const hasConfig = await userConfigManager.hasConfig();
5416
+ if (!hasConfig) {
5417
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5418
+ return;
5419
+ }
5420
+ const answers = await inquirer10.prompt([
5421
+ {
5422
+ type: "confirm",
5423
+ name: "confirm",
5424
+ message: "\u786E\u5B9A\u8981\u5220\u9664 GitLab \u914D\u7F6E\u5417?",
5425
+ default: false
5426
+ }
5427
+ ]);
5428
+ if (!answers.confirm) {
5429
+ logger.info("\u5DF2\u53D6\u6D88");
5430
+ return;
5431
+ }
5432
+ await userConfigManager.removeConfig();
5433
+ logger.success("\u914D\u7F6E\u5DF2\u5220\u9664");
5434
+ } catch (error) {
5435
+ logger.error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error.message}`);
5436
+ if (process.env.DEBUG) {
5437
+ console.error(error);
5438
+ }
5439
+ process.exit(1);
5440
+ }
5441
+ });
5442
+ validateTokenCommand = new Command14("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
5443
+ try {
5444
+ logger.header("\u9A8C\u8BC1 GitLab Token");
5445
+ logger.newLine();
5446
+ const config = await userConfigManager.getGitLabConfig();
5447
+ if (!config) {
5448
+ logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
5449
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5450
+ process.exit(1);
5451
+ }
5452
+ logger.info("\u6B63\u5728\u9A8C\u8BC1...");
5453
+ const gitlabAPI = new GitLabAPI(config);
5454
+ const isValid = await gitlabAPI.authenticate();
5455
+ if (isValid) {
5456
+ logger.success("Token \u6709\u6548!");
5457
+ logger.newLine();
5458
+ logger.info(`GitLab URL: ${chalk2.cyan(config.baseUrl)}`);
5459
+ } else {
5460
+ logger.error("Token \u9A8C\u8BC1\u5931\u8D25");
5461
+ logger.info("\u8BF7\u68C0\u67E5 Token \u662F\u5426\u6B63\u786E\u6216\u5DF2\u8FC7\u671F");
5462
+ process.exit(1);
5463
+ }
5464
+ } catch (error) {
5465
+ logger.error(`\u9A8C\u8BC1\u5931\u8D25: ${error.message}`);
5466
+ if (process.env.DEBUG) {
5467
+ console.error(error);
5468
+ }
5469
+ process.exit(1);
5470
+ }
5471
+ });
5472
+ configCommand = new Command14("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
5473
+ }
5474
+ });
5475
+
5476
+ // src/commands/diff.ts
5477
+ import { Command as Command15 } from "commander";
5478
+ import chalk3 from "chalk";
5479
+ async function compareTemplate(projectPath, type, localConfig, remoteTag, remoteBranch, gitlabConfig) {
5480
+ try {
5481
+ logger.step(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
5482
+ const repository = localConfig.repository;
5483
+ const localCommit = localConfig.commit;
5484
+ const localTag = localConfig.tag;
5485
+ const localBranch = localConfig.branch;
5486
+ let remoteCommit = null;
5487
+ let remoteTagName;
5488
+ let remoteBranchName;
5489
+ if (gitlabConfig) {
5490
+ const gitlabAPI = new GitLabAPI(gitlabConfig);
5491
+ const projectPathEncoded = GitLabAPI.parseProjectPath(repository);
5492
+ if (remoteTag) {
5493
+ remoteCommit = await gitlabAPI.getTagCommit(projectPathEncoded, remoteTag);
5494
+ remoteTagName = remoteTag;
5495
+ } else if (remoteBranch) {
5496
+ remoteCommit = await gitlabAPI.getBranchCommit(projectPathEncoded, remoteBranch);
5497
+ remoteBranchName = remoteBranch;
5498
+ } else {
5499
+ const latest = await gitlabAPI.getLatestVersion(projectPathEncoded);
5500
+ if (latest) {
5501
+ remoteCommit = latest.commit;
5502
+ if (latest.type === "tag") {
5503
+ remoteTagName = latest.name;
5504
+ } else {
5505
+ remoteBranchName = latest.name;
5506
+ }
5507
+ }
5508
+ }
5509
+ if (remoteCommit && localCommit && remoteCommit !== localCommit) {
5510
+ const from = localTag || localBranch || localCommit;
5511
+ const to = remoteTagName || remoteBranchName || remoteCommit;
5512
+ const diff = await gitlabAPI.compareVersions(projectPathEncoded, from, to);
5513
+ return {
5514
+ type,
5515
+ local: {
5516
+ commit: localCommit,
5517
+ tag: localTag,
5518
+ branch: localBranch
5519
+ },
5520
+ remote: {
5521
+ commit: remoteCommit,
5522
+ tag: remoteTagName,
5523
+ branch: remoteBranchName
5524
+ },
5525
+ diff: diff || void 0
5526
+ };
5527
+ }
5528
+ }
5529
+ if (!remoteCommit) {
5530
+ const { execa: execa5 } = await import("execa");
5531
+ const ref = remoteTag || remoteBranch || "HEAD";
5532
+ const { stdout } = await execa5("git", ["ls-remote", repository, ref], {
5533
+ stdio: "pipe"
5534
+ });
5535
+ remoteCommit = stdout.split(" ")[0];
5536
+ remoteTagName = remoteTag;
5537
+ remoteBranchName = remoteBranch;
5538
+ }
5539
+ const needsUpdate = localCommit !== remoteCommit;
5540
+ logger.info(
5541
+ `${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${needsUpdate ? chalk3.yellow("\u6709\u66F4\u65B0") : chalk3.green("\u5DF2\u662F\u6700\u65B0")}`
5542
+ );
5543
+ return {
5544
+ type,
5545
+ local: {
5546
+ commit: localCommit,
5547
+ tag: localTag,
5548
+ branch: localBranch
5549
+ },
5550
+ remote: {
5551
+ commit: remoteCommit || void 0,
5552
+ tag: remoteTagName,
5553
+ branch: remoteBranchName
5554
+ }
5555
+ };
5556
+ } catch (error) {
5557
+ logger.error(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5931\u8D25: ${error}`);
5558
+ return null;
5559
+ }
5560
+ }
5561
+ function outputTable(results) {
5562
+ logger.newLine();
5563
+ for (const result of results) {
5564
+ const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
5565
+ console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
5566
+ console.log("");
5567
+ const t = new Table({
5568
+ head: [chalk3.cyan("\u9879\u76EE"), chalk3.cyan("\u7248\u672C")],
5569
+ colWidths: [20, 50]
5570
+ });
5571
+ const localVersion = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "-";
5572
+ const localInfo = `Commit: ${result.local.commit?.substring(0, 8) || "-"}${result.local.tag ? `
5573
+ Tag: ${result.local.tag}` : ""}${result.local.branch ? `
5574
+ Branch: ${result.local.branch}` : ""}`;
5575
+ t.push(["\u672C\u5730", localInfo]);
5576
+ const remoteVersion = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "-";
5577
+ const remoteInfo = `Commit: ${result.remote.commit?.substring(0, 8) || "-"}${result.remote.tag ? `
5578
+ Tag: ${result.remote.tag}` : ""}${result.remote.branch ? `
5579
+ Branch: ${result.remote.branch}` : ""}`;
5580
+ t.push(["\u8FDC\u7A0B", remoteInfo]);
5581
+ console.log(t.toString());
5582
+ if (result.diff && result.diff.commits.length > 0) {
5583
+ console.log("");
5584
+ console.log(chalk3.bold("\u65B0\u589E\u63D0\u4EA4:"));
5585
+ const commitsTable = new Table({
5586
+ head: [chalk3.cyan("Commit"), chalk3.cyan("\u4F5C\u8005"), chalk3.cyan("\u65F6\u95F4"), chalk3.cyan("\u63CF\u8FF0")],
5587
+ colWidths: [10, 15, 20, 50],
5588
+ wordWrap: true
5589
+ });
5590
+ for (const commit of result.diff.commits.slice(0, 10)) {
5591
+ commitsTable.push([
5592
+ commit.short_id,
5593
+ commit.author_name,
5594
+ new Date(commit.created_at).toLocaleDateString("zh-CN"),
5595
+ commit.title
5596
+ ]);
5597
+ }
5598
+ console.log(commitsTable.toString());
5599
+ if (result.diff.commits.length > 10) {
5600
+ console.log(chalk3.gray(`... \u8FD8\u6709 ${result.diff.commits.length - 10} \u4E2A\u63D0\u4EA4`));
5601
+ }
5602
+ const files = result.diff.diffs;
5603
+ const added = files.filter((f) => f.new_file).length;
5604
+ const deleted = files.filter((f) => f.deleted_file).length;
5605
+ const modified = files.length - added - deleted;
5606
+ console.log("");
5607
+ console.log(chalk3.bold("\u6587\u4EF6\u53D8\u66F4:"));
5608
+ console.log(` ${chalk3.green("+")} \u65B0\u589E: ${added}`);
5609
+ console.log(` ${chalk3.red("-")} \u5220\u9664: ${deleted}`);
5610
+ console.log(` ${chalk3.yellow("~")} \u4FEE\u6539: ${modified}`);
5611
+ }
5612
+ console.log("");
5613
+ }
5614
+ }
5615
+ function outputJson(results) {
5616
+ console.log(JSON.stringify(results, null, 2));
5617
+ }
5618
+ function outputDiff(results) {
5619
+ logger.newLine();
5620
+ for (const result of results) {
5621
+ const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
5622
+ console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
5623
+ console.log("");
5624
+ const from = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "unknown";
5625
+ const to = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "unknown";
5626
+ console.log(`\u672C\u5730\u7248\u672C: ${chalk3.cyan(from)}`);
5627
+ console.log(`\u8FDC\u7A0B\u7248\u672C: ${chalk3.cyan(to)}`);
5628
+ if (result.diff && result.diff.commits.length > 0) {
5629
+ console.log("");
5630
+ console.log(chalk3.bold("\u63D0\u4EA4\u5386\u53F2:"));
5631
+ for (const commit of result.diff.commits) {
5632
+ console.log("");
5633
+ console.log(chalk3.yellow(`commit ${commit.id}`));
5634
+ console.log(`Author: ${commit.author_name} <${commit.author_email}>`);
5635
+ console.log(`Date: ${new Date(commit.created_at).toLocaleString("zh-CN")}`);
5636
+ console.log("");
5637
+ console.log(` ${commit.title}`);
5638
+ if (commit.message) {
5639
+ console.log(` ${commit.message.split("\n").join("\n ")}`);
5640
+ }
5641
+ }
5642
+ }
5643
+ console.log("");
5644
+ }
5645
+ }
5646
+ var diffCommand, Table;
5647
+ var init_diff = __esm({
5648
+ "src/commands/diff.ts"() {
5649
+ "use strict";
5650
+ init_esm_shims();
5651
+ init_template_version();
5652
+ init_logger();
5653
+ init_utils();
5654
+ init_user_config();
5655
+ init_gitlab_api();
5656
+ 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) => {
5657
+ try {
5658
+ logger.header("\u6A21\u677F\u7248\u672C\u5BF9\u6BD4");
5659
+ logger.newLine();
5660
+ const hasConfig = await FileUtils.exists("TECH_STACK.md");
5661
+ if (!hasConfig) {
5662
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
5663
+ logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
5664
+ process.exit(1);
5665
+ }
5666
+ const projectPath = ".";
5667
+ const checkAll = !options.frontend && !options.backend;
5668
+ const checkFrontend = options.frontend || checkAll;
5669
+ const checkBackend = options.backend || checkAll;
5670
+ const config = await readTemplateConfig(projectPath);
5671
+ if (!config) {
5672
+ logger.error("\u672A\u627E\u5230\u6A21\u677F\u914D\u7F6E");
5673
+ process.exit(1);
5674
+ }
5675
+ const gitlabConfig = await userConfigManager.getGitLabConfig();
5676
+ if (!gitlabConfig) {
5677
+ logger.warn("\u672A\u914D\u7F6E GitLab Token");
5678
+ logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
5679
+ logger.info("\u5C06\u4F7F\u7528\u57FA\u672C git \u547D\u4EE4\u8FDB\u884C\u5BF9\u6BD4");
5680
+ }
5681
+ const results = [];
5682
+ if (checkFrontend && config.frontend) {
5683
+ const result = await compareTemplate(
5684
+ projectPath,
5685
+ "frontend",
5686
+ config.frontend,
5687
+ options.tag,
5688
+ options.branch,
5689
+ gitlabConfig || void 0
5690
+ );
5691
+ if (result) {
5692
+ results.push(result);
5693
+ }
5694
+ }
5695
+ if (checkBackend && config.backend) {
5696
+ const result = await compareTemplate(
5697
+ projectPath,
5698
+ "backend",
5699
+ config.backend,
5700
+ options.tag,
5701
+ options.branch,
5702
+ gitlabConfig || void 0
5703
+ );
5704
+ if (result) {
5705
+ results.push(result);
5706
+ }
5707
+ }
5708
+ if (results.length === 0) {
5709
+ logger.info("\u6CA1\u6709\u53EF\u5BF9\u6BD4\u7684\u6A21\u677F");
5710
+ return;
5711
+ }
5712
+ switch (options.output) {
5713
+ case "json":
5714
+ outputJson(results);
5715
+ break;
5716
+ case "diff":
5717
+ outputDiff(results);
5718
+ break;
5719
+ case "table":
5720
+ default:
5721
+ outputTable(results);
5722
+ break;
5723
+ }
5724
+ } catch (error) {
5725
+ logger.error(`\u5BF9\u6BD4\u5931\u8D25: ${error.message}`);
5726
+ if (process.env.DEBUG) {
5727
+ console.error(error);
5728
+ }
5729
+ process.exit(1);
5730
+ }
5731
+ });
5732
+ Table = class {
5733
+ options;
5734
+ rows = [];
5735
+ constructor(options) {
5736
+ this.options = options;
5737
+ }
5738
+ push(row) {
5739
+ this.rows.push(row);
5740
+ }
5741
+ toString() {
5742
+ if (this.rows.length === 0) return "";
5743
+ let result = "";
5744
+ const colWidths = this.options.colWidths || [];
5745
+ if (this.options.head) {
5746
+ result += "| ";
5747
+ for (let i = 0; i < this.options.head.length; i++) {
5748
+ const width = colWidths[i] || 20;
5749
+ const cell = this.options.head[i] || "";
5750
+ result += cell.padEnd(width) + " | ";
5751
+ }
5752
+ result += "\n";
5753
+ result += "|";
5754
+ for (let i = 0; i < this.options.head.length; i++) {
5755
+ const width = colWidths[i] || 20;
5756
+ result += "-".repeat(width + 2) + "|";
5757
+ }
5758
+ result += "\n";
5759
+ }
5760
+ for (const row of this.rows) {
5761
+ result += "| ";
5762
+ for (let i = 0; i < row.length; i++) {
5763
+ const width = colWidths[i] || 20;
5764
+ const cell = String(row[i] || "");
5765
+ const displayCell = this.options.wordWrap && cell.length > width ? cell.substring(0, width - 3) + "..." : cell;
5766
+ result += displayCell.padEnd(width) + " | ";
5767
+ }
5768
+ result += "\n";
5769
+ }
5770
+ return result;
5771
+ }
5772
+ };
5773
+ }
5774
+ });
5775
+
5776
+ // src/index.ts
5777
+ var index_exports = {};
5778
+ import { Command as Command16 } from "commander";
5779
+ import chalk4 from "chalk";
4396
5780
  function showHelp() {
4397
5781
  console.log("");
4398
5782
  logger.header("team-cli - AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6");
4399
5783
  console.log("");
4400
- console.log(chalk2.bold("\u4F7F\u7528\u65B9\u6CD5:"));
5784
+ console.log(chalk4.bold("\u4F7F\u7528\u65B9\u6CD5:"));
4401
5785
  console.log(" team-cli init [project-name] \u521D\u59CB\u5316\u65B0\u9879\u76EE");
4402
5786
  console.log(" team-cli split-prd <prd-folder> \u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs");
4403
5787
  console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
@@ -4411,21 +5795,24 @@ function showHelp() {
4411
5795
  console.log(" team-cli status \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4412
5796
  console.log(" team-cli lint \u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF+\u540E\u7AEF)");
4413
5797
  console.log(" team-cli logs [date] \u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7");
5798
+ console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
5799
+ console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
5800
+ console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
4414
5801
  console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
4415
5802
  console.log("");
4416
- console.log(chalk2.bold("\u793A\u4F8B:"));
5803
+ console.log(chalk4.bold("\u793A\u4F8B:"));
4417
5804
  console.log(" team-cli init my-project");
4418
5805
  console.log(" cd my-project");
4419
5806
  console.log(" team-cli add-feature payment-system");
4420
5807
  console.log(" team-cli breakdown docs/specs/xxx.md");
4421
5808
  console.log(" team-cli dev");
4422
5809
  console.log("");
4423
- console.log(chalk2.bold("\u5F00\u53D1\u6D41\u7A0B:"));
5810
+ console.log(chalk4.bold("\u5F00\u53D1\u6D41\u7A0B:"));
4424
5811
  console.log(" 1. PRD \u2192 specs (split-prd)");
4425
5812
  console.log(" 2. spec \u2192 milestones + todos (breakdown)");
4426
5813
  console.log(" 3. \u9009\u62E9 milestone/todo \u2192 \u5B9E\u73B0 (dev)");
4427
5814
  console.log("");
4428
- console.log(chalk2.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
5815
+ console.log(chalk4.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
4429
5816
  console.log(" team-cli add-feature <name> # \u6DFB\u52A0\u65B0\u529F\u80FD");
4430
5817
  console.log(" team-cli detect-deps [spec] # \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
4431
5818
  console.log(" team-cli sync-memory # \u540C\u6B65 AI_MEMORY");
@@ -4434,7 +5821,14 @@ function showHelp() {
4434
5821
  console.log(" team-cli hotfix # \u7D27\u6025\u4FEE\u590D");
4435
5822
  console.log(" team-cli status # \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4436
5823
  console.log("");
4437
- console.log(chalk2.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
5824
+ console.log(chalk4.bold("\u6A21\u677F\u7BA1\u7406:"));
5825
+ console.log(" team-cli config set-token # \u8BBE\u7F6E GitLab Access Token");
5826
+ console.log(" team-cli config show # \u663E\u793A\u5F53\u524D\u914D\u7F6E");
5827
+ console.log(" team-cli init --tag v1.0.0 # \u4F7F\u7528\u6307\u5B9A tag \u521D\u59CB\u5316");
5828
+ console.log(" team-cli update --tag v1.1.0 # \u66F4\u65B0\u5230\u6307\u5B9A\u7248\u672C");
5829
+ console.log(" team-cli diff # \u5BF9\u6BD4\u6A21\u677F\u5DEE\u5F02");
5830
+ console.log("");
5831
+ console.log(chalk4.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
4438
5832
  console.log("");
4439
5833
  }
4440
5834
  var program;
@@ -4456,8 +5850,11 @@ var init_index = __esm({
4456
5850
  init_sync_memory();
4457
5851
  init_check_api();
4458
5852
  init_logs();
4459
- program = new Command13();
4460
- program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version("2.0.0");
5853
+ init_update();
5854
+ init_config();
5855
+ init_diff();
5856
+ program = new Command16();
5857
+ program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version("2.1.1");
4461
5858
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
4462
5859
  program.addCommand(initCommand);
4463
5860
  program.addCommand(splitPrdCommand);
@@ -4472,6 +5869,9 @@ var init_index = __esm({
4472
5869
  program.addCommand(syncMemoryCommand);
4473
5870
  program.addCommand(checkApiCommand);
4474
5871
  program.addCommand(logsCommand);
5872
+ program.addCommand(updateCommand);
5873
+ program.addCommand(configCommand);
5874
+ program.addCommand(diffCommand);
4475
5875
  program.action(() => {
4476
5876
  showHelp();
4477
5877
  });