yg-team-cli 2.5.8 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -422,7 +422,89 @@ var init_utils = __esm({
422
422
  return spec;
423
423
  }
424
424
  /**
425
- * 获取 spec 状态
425
+ * 解析里程碑信息
426
+ */
427
+ static parseMilestones(content) {
428
+ const milestones = [];
429
+ const lines = content.split("\n");
430
+ let currentMilestone = null;
431
+ let inMilestone = false;
432
+ for (const line of lines) {
433
+ if (line.match(/^###\s+Milestone\s+\d+:/)) {
434
+ if (currentMilestone) {
435
+ milestones.push(currentMilestone);
436
+ }
437
+ const title = line.replace(/^###\s+/, "").trim();
438
+ currentMilestone = { title, todos: [], completedCount: 0 };
439
+ inMilestone = true;
440
+ continue;
441
+ }
442
+ if (inMilestone && currentMilestone) {
443
+ if (line.match(/^###\s+Milestone/)) {
444
+ milestones.push(currentMilestone);
445
+ currentMilestone = null;
446
+ continue;
447
+ }
448
+ const todoMatch = line.match(/^-\s+\[([ xX])\]\s*(.+)/);
449
+ if (todoMatch) {
450
+ const isCompleted = todoMatch[1].toLowerCase() === "x";
451
+ if (isCompleted) {
452
+ currentMilestone.completedCount++;
453
+ }
454
+ currentMilestone.todos.push(todoMatch[2].trim());
455
+ }
456
+ }
457
+ }
458
+ if (currentMilestone) {
459
+ milestones.push(currentMilestone);
460
+ }
461
+ return milestones;
462
+ }
463
+ /**
464
+ * 解析 spec 状态(基于进度动态推导)
465
+ */
466
+ static parseSpecStatus(content) {
467
+ const milestones = this.parseMilestones(content);
468
+ if (milestones.length > 0) {
469
+ const totalTodos = milestones.reduce((sum, m) => sum + m.todos.length, 0);
470
+ const completedTodos = milestones.reduce((sum, m) => sum + m.completedCount, 0);
471
+ if (totalTodos > 0) {
472
+ if (completedTodos === totalTodos) return "\u5DF2\u5B8C\u6210";
473
+ if (completedTodos > 0) return "\u8FDB\u884C\u4E2D";
474
+ }
475
+ return "\u5DF2\u62C6\u5206";
476
+ }
477
+ const statusMatch = content.match(/状态.*[::]\s*(.+)/);
478
+ if (statusMatch) {
479
+ const status = statusMatch[1].replace(/\*\*/g, "").trim();
480
+ if (status.includes("\u5DF2\u5B8C\u6210")) return "\u5DF2\u5B8C\u6210";
481
+ if (status.includes("\u8FDB\u884C\u4E2D")) return "\u8FDB\u884C\u4E2D";
482
+ if (status.includes("\u5DF2\u62C6\u5206")) return "\u5DF2\u62C6\u5206";
483
+ }
484
+ return "\u672A\u5F00\u59CB";
485
+ }
486
+ /**
487
+ * 获取进度字符串
488
+ */
489
+ static getProgress(content) {
490
+ const milestones = this.parseMilestones(content);
491
+ if (milestones.length === 0) return "-";
492
+ const totalTodos = milestones.reduce((sum, m) => sum + m.todos.length, 0);
493
+ const completedTodos = milestones.reduce((sum, m) => sum + m.completedCount, 0);
494
+ if (totalTodos === 0) return "-";
495
+ return `${completedTodos}/${totalTodos}`;
496
+ }
497
+ /**
498
+ * 获取带图标的状态
499
+ */
500
+ static getStatusWithIcon(status) {
501
+ if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2713 \u5DF2\u5B8C\u6210";
502
+ if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
503
+ if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
504
+ return "\u25CB \u672A\u5F00\u59CB";
505
+ }
506
+ /**
507
+ * 获取 spec 状态 (原始版本兼容)
426
508
  */
427
509
  static async getSpecStatus(file) {
428
510
  try {
@@ -2786,7 +2868,7 @@ async function selectSpec() {
2786
2868
  for (let i = 0; i < specFiles.length; i++) {
2787
2869
  const file = path9.join(specDir, specFiles[i]);
2788
2870
  const spec = await FileUtils.read(file);
2789
- const status = parseSpecStatus(spec);
2871
+ const status = SpecUtils.parseSpecStatus(spec);
2790
2872
  const dependencies = parseDependencies(spec);
2791
2873
  specs.push({
2792
2874
  file,
@@ -2828,7 +2910,7 @@ async function selectMilestone(specFile) {
2828
2910
  logger.step("\u6B65\u9AA4 2/3: \u89E3\u6790 milestones...");
2829
2911
  logger.newLine();
2830
2912
  const specContent = await FileUtils.read(specFile);
2831
- const milestones = parseMilestones(specContent);
2913
+ const milestones = SpecUtils.parseMilestones(specContent);
2832
2914
  if (milestones.length === 0) {
2833
2915
  logger.info("\u8BE5 spec \u5C1A\u672A\u62C6\u5206 milestones");
2834
2916
  const { breakdownNow } = await inquirer3.prompt([
@@ -2881,7 +2963,9 @@ async function selectTodo(specFile, milestone) {
2881
2963
  logger.step("\u6B65\u9AA4 3/3: \u9009\u62E9 todo \u4EFB\u52A1...");
2882
2964
  logger.newLine();
2883
2965
  const specContent = await FileUtils.read(specFile);
2884
- const todos = parseTodos(specContent, milestone);
2966
+ const milestones = SpecUtils.parseMilestones(specContent);
2967
+ const targetMilestone = milestones.find((m) => m.title === milestone);
2968
+ const todos = targetMilestone ? targetMilestone.todos : [];
2885
2969
  if (todos.length === 0) {
2886
2970
  logger.warn("\u8BE5 milestone \u6CA1\u6709 todo \u4EFB\u52A1");
2887
2971
  const { implementAll } = await inquirer3.prompt([
@@ -2955,16 +3039,6 @@ async function executeDevelopment(specFile, milestone, todo) {
2955
3039
  logger.step("2. \u8FD0\u884C 'team-cli dev' \u7EE7\u7EED\u4E0B\u4E00\u4E2A\u4EFB\u52A1");
2956
3040
  logger.newLine();
2957
3041
  }
2958
- function parseSpecStatus(spec) {
2959
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
2960
- if (statusMatch) {
2961
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
2962
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u5DF2\u5B8C\u6210";
2963
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u8FDB\u884C\u4E2D";
2964
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u5DF2\u62C6\u5206";
2965
- }
2966
- return "\u672A\u5F00\u59CB";
2967
- }
2968
3042
  function parseDependencies(spec) {
2969
3043
  const deps = [];
2970
3044
  let inDepsSection = false;
@@ -2987,63 +3061,6 @@ function parseDependencies(spec) {
2987
3061
  }
2988
3062
  return deps;
2989
3063
  }
2990
- function parseMilestones(spec) {
2991
- const milestones = [];
2992
- const lines = spec.split("\n");
2993
- let currentMilestone = null;
2994
- let inMilestone = false;
2995
- for (const line of lines) {
2996
- if (line.match(/^###\s+Milestone\s+\d+:/)) {
2997
- if (currentMilestone) {
2998
- milestones.push(currentMilestone);
2999
- }
3000
- const title = line.replace(/^###\s+/, "").trim();
3001
- currentMilestone = { title, todos: [], completedCount: 0 };
3002
- inMilestone = true;
3003
- continue;
3004
- }
3005
- if (inMilestone && currentMilestone) {
3006
- if (line.match(/^###\s+Milestone/)) {
3007
- milestones.push(currentMilestone);
3008
- currentMilestone = null;
3009
- continue;
3010
- }
3011
- const todoMatch = line.match(/^-\s+\[([ xX])\]\s*(.+)/);
3012
- if (todoMatch) {
3013
- const isCompleted = todoMatch[1].toLowerCase() === "x";
3014
- if (isCompleted) {
3015
- currentMilestone.completedCount++;
3016
- }
3017
- currentMilestone.todos.push(todoMatch[2].trim());
3018
- }
3019
- }
3020
- }
3021
- if (currentMilestone) {
3022
- milestones.push(currentMilestone);
3023
- }
3024
- return milestones;
3025
- }
3026
- function parseTodos(spec, milestoneTitle) {
3027
- const lines = spec.split("\n");
3028
- const todos = [];
3029
- let inTargetMilestone = false;
3030
- for (const line of lines) {
3031
- if (line.includes(milestoneTitle)) {
3032
- inTargetMilestone = true;
3033
- continue;
3034
- }
3035
- if (inTargetMilestone) {
3036
- if (line.match(/^###\s+Milestone/)) {
3037
- break;
3038
- }
3039
- const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
3040
- if (todoMatch) {
3041
- todos.push(todoMatch[1].trim());
3042
- }
3043
- }
3044
- }
3045
- return todos;
3046
- }
3047
3064
  function topologicalSort(specs) {
3048
3065
  const sorted = [];
3049
3066
  const visited = /* @__PURE__ */ new Set();
@@ -3201,7 +3218,19 @@ async function askAndUpdateSpecStatus(specFile, milestone, todo) {
3201
3218
  logger.warn("\u672A\u627E\u5230\u5BF9\u5E94\u7684 todo \u9879");
3202
3219
  return;
3203
3220
  }
3204
- await FileUtils.write(specFile, lines.join("\n"));
3221
+ const newContent = lines.join("\n");
3222
+ const computedStatus = SpecUtils.parseSpecStatus(newContent);
3223
+ let finalLines = lines;
3224
+ const statusLineIndex = finalLines.findIndex((l) => l.match(/状态.*[::]/));
3225
+ if (statusLineIndex !== -1) {
3226
+ const oldLine = finalLines[statusLineIndex];
3227
+ const newLine = oldLine.replace(/([::]\s*)(.+)/, `$1**${computedStatus}**`);
3228
+ if (oldLine !== newLine) {
3229
+ finalLines[statusLineIndex] = newLine;
3230
+ logger.info(`\u81EA\u52A8\u540C\u6B65 Spec \u5934\u90E8\u72B6\u6001\u4E3A: ${computedStatus}`);
3231
+ }
3232
+ }
3233
+ await FileUtils.write(specFile, finalLines.join("\n"));
3205
3234
  logger.success("Spec \u6587\u4EF6\u5DF2\u66F4\u65B0");
3206
3235
  } catch (error) {
3207
3236
  logger.warn(`\u66F4\u65B0 spec \u6587\u4EF6\u5931\u8D25: ${error}`);
@@ -4581,11 +4610,12 @@ async function displayFeatureInventory() {
4581
4610
  for (const file of specs) {
4582
4611
  const filePath = path13.join(specDir, file);
4583
4612
  const content = await FileUtils.read(filePath);
4584
- const status = parseSpecStatus2(content);
4613
+ const status = SpecUtils.parseSpecStatus(content);
4614
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
4585
4615
  inventory.push({
4586
4616
  name: file.replace(".md", ""),
4587
- status,
4588
- progress: getProgress(content)
4617
+ status: statusWithIcon,
4618
+ progress: SpecUtils.getProgress(content)
4589
4619
  });
4590
4620
  }
4591
4621
  const tableData = inventory.map((item) => [
@@ -4652,28 +4682,6 @@ async function displayRecentActivity() {
4652
4682
  logger.newLine();
4653
4683
  }
4654
4684
  }
4655
- function parseSpecStatus2(spec) {
4656
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
4657
- if (statusMatch) {
4658
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
4659
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2713 \u5DF2\u5B8C\u6210";
4660
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
4661
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
4662
- }
4663
- return "\u25CB \u672A\u5F00\u59CB";
4664
- }
4665
- function getProgress(spec) {
4666
- const milestoneMatches = spec.match(/###\s+Milestone\s+\d+:/g);
4667
- const milestones = milestoneMatches ? milestoneMatches.length : 0;
4668
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
4669
- const totalTodos = todoMatches ? todoMatches.length : 0;
4670
- const completedMatches = spec.match(/-\s+\[x\]/g);
4671
- const completedTodos = completedMatches ? completedMatches.length : 0;
4672
- if (totalTodos === 0) {
4673
- return "-";
4674
- }
4675
- return `${completedTodos}/${totalTodos}`;
4676
- }
4677
4685
 
4678
4686
  // src/commands/detect-deps.ts
4679
4687
  init_esm_shims();
@@ -4991,16 +4999,17 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
4991
4999
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4992
5000
  const specPath = path15.join(specsDir, specFile);
4993
5001
  const content = await FileUtils.read(specPath);
4994
- const status = parseSpecStatus3(content);
4995
- const progress = getSpecProgress(content);
5002
+ const status = SpecUtils.parseSpecStatus(content);
5003
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
5004
+ const progress = SpecUtils.getProgress(content);
4996
5005
  let completionDate = "-";
4997
- if (status === "\u2705 \u5DF2\u5B8C\u6210") {
5006
+ if (status === "\u5DF2\u5B8C\u6210") {
4998
5007
  const dateMatch = content.match(/完成日期.*[::]\s*(.+)/);
4999
5008
  if (dateMatch) {
5000
5009
  completionDate = dateMatch[1].trim();
5001
5010
  }
5002
5011
  }
5003
- lines.push(`| ${displayName} | ${name}.md | ${status} | ${progress} | ${completionDate} | |`);
5012
+ lines.push(`| ${displayName} | ${name}.md | ${statusWithIcon} | ${progress} | ${completionDate} | |`);
5004
5013
  }
5005
5014
  const newContent = lines.join("\n") + "\n";
5006
5015
  await replaceOrInsertSection(aiMemoryFile, "## \u529F\u80FD\u6E05\u5355", newContent);
@@ -5187,26 +5196,6 @@ async function replaceOrInsertSection(aiMemoryFile, sectionTitle, newContent) {
5187
5196
  await FileUtils.write(aiMemoryFile, content.trimEnd() + "\n\n" + newContent + "\n");
5188
5197
  }
5189
5198
  }
5190
- function parseSpecStatus3(spec) {
5191
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
5192
- if (statusMatch) {
5193
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
5194
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2705 \u5DF2\u5B8C\u6210";
5195
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
5196
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
5197
- }
5198
- return "\u25CB \u672A\u5F00\u59CB";
5199
- }
5200
- function getSpecProgress(spec) {
5201
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
5202
- const totalTodos = todoMatches ? todoMatches.length : 0;
5203
- const completedMatches = spec.match(/-\s+\[x\]/g);
5204
- const completedTodos = completedMatches ? completedMatches.length : 0;
5205
- if (totalTodos === 0) {
5206
- return "-";
5207
- }
5208
- return `${completedTodos}/${totalTodos}`;
5209
- }
5210
5199
  async function syncTemplateVersions(aiMemoryFile, projectDir) {
5211
5200
  logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
5212
5201
  const config = await readTemplateConfig(projectDir);