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/README.md CHANGED
@@ -69,6 +69,16 @@
69
69
  - **新增**: `team-cli dev` 选择里程碑时,现在会直观显示已完成、进行中和未开始的状态图标及进度 (如 `[4/7]`)
70
70
  - **改进**: 增强了 Spec 文件的解析逻辑,能够更准确地识别已标记为 `[x]` 的任务项
71
71
 
72
+ **v2.5.9** - Spec 状态同步与动态检测
73
+ - **改进**: `dev` 模式列表现在基于 Milestone 实际完成百分比动态显示 Spec 状态(即使文件头部状态未及时手动更新)
74
+ - **修复**: 在通过 `dev` 命令完成任务时,系统现在会自动同步并更新 Spec 文件头部的“状态”标识,确保文档与进度一致
75
+
76
+ **v2.6.0** - 统一状态解析逻辑与多维同步
77
+ - **重构**: 将核心 Spec 解析逻辑沉淀至 `SpecUtils` 共享工具类,实现全系统逻辑收口
78
+ - **改进**: `status` 命令与 `sync-memory` 命令现在统一接入动态进度推导引擎,解决状态显示落后的问题
79
+ - **优化**: 全面统一了各命令中状态图标(✓, ⟳, ◉, ○)的定义,提供一致的视觉交互体验
80
+ - **增强**: `sync-memory` 现在能更准确地根据任务勾选情况自动更新 `AI_MEMORY.md` 中的完成日期及状态列
81
+
72
82
  **v2.4.10** - 修复 Claude 返回权限确认而非实际内容问题
73
83
  - **修复**: 添加 `--dangerously-skip-permissions` 参数跳过权限确认
74
84
  - **问题**: v2.4.9 中 Claude CLI 在新目录首次运行时返回权限确认提示而非 spec 内容
package/dist/cli.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 {
@@ -2775,7 +2857,7 @@ async function selectSpec() {
2775
2857
  for (let i = 0; i < specFiles.length; i++) {
2776
2858
  const file = path9.join(specDir, specFiles[i]);
2777
2859
  const spec = await FileUtils.read(file);
2778
- const status = parseSpecStatus(spec);
2860
+ const status = SpecUtils.parseSpecStatus(spec);
2779
2861
  const dependencies = parseDependencies(spec);
2780
2862
  specs.push({
2781
2863
  file,
@@ -2817,7 +2899,7 @@ async function selectMilestone(specFile) {
2817
2899
  logger.step("\u6B65\u9AA4 2/3: \u89E3\u6790 milestones...");
2818
2900
  logger.newLine();
2819
2901
  const specContent = await FileUtils.read(specFile);
2820
- const milestones = parseMilestones(specContent);
2902
+ const milestones = SpecUtils.parseMilestones(specContent);
2821
2903
  if (milestones.length === 0) {
2822
2904
  logger.info("\u8BE5 spec \u5C1A\u672A\u62C6\u5206 milestones");
2823
2905
  const { breakdownNow } = await inquirer3.prompt([
@@ -2870,7 +2952,9 @@ async function selectTodo(specFile, milestone) {
2870
2952
  logger.step("\u6B65\u9AA4 3/3: \u9009\u62E9 todo \u4EFB\u52A1...");
2871
2953
  logger.newLine();
2872
2954
  const specContent = await FileUtils.read(specFile);
2873
- const todos = parseTodos(specContent, milestone);
2955
+ const milestones = SpecUtils.parseMilestones(specContent);
2956
+ const targetMilestone = milestones.find((m) => m.title === milestone);
2957
+ const todos = targetMilestone ? targetMilestone.todos : [];
2874
2958
  if (todos.length === 0) {
2875
2959
  logger.warn("\u8BE5 milestone \u6CA1\u6709 todo \u4EFB\u52A1");
2876
2960
  const { implementAll } = await inquirer3.prompt([
@@ -2944,16 +3028,6 @@ async function executeDevelopment(specFile, milestone, todo) {
2944
3028
  logger.step("2. \u8FD0\u884C 'team-cli dev' \u7EE7\u7EED\u4E0B\u4E00\u4E2A\u4EFB\u52A1");
2945
3029
  logger.newLine();
2946
3030
  }
2947
- function parseSpecStatus(spec) {
2948
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
2949
- if (statusMatch) {
2950
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
2951
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u5DF2\u5B8C\u6210";
2952
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u8FDB\u884C\u4E2D";
2953
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u5DF2\u62C6\u5206";
2954
- }
2955
- return "\u672A\u5F00\u59CB";
2956
- }
2957
3031
  function parseDependencies(spec) {
2958
3032
  const deps = [];
2959
3033
  let inDepsSection = false;
@@ -2976,63 +3050,6 @@ function parseDependencies(spec) {
2976
3050
  }
2977
3051
  return deps;
2978
3052
  }
2979
- function parseMilestones(spec) {
2980
- const milestones = [];
2981
- const lines = spec.split("\n");
2982
- let currentMilestone = null;
2983
- let inMilestone = false;
2984
- for (const line of lines) {
2985
- if (line.match(/^###\s+Milestone\s+\d+:/)) {
2986
- if (currentMilestone) {
2987
- milestones.push(currentMilestone);
2988
- }
2989
- const title = line.replace(/^###\s+/, "").trim();
2990
- currentMilestone = { title, todos: [], completedCount: 0 };
2991
- inMilestone = true;
2992
- continue;
2993
- }
2994
- if (inMilestone && currentMilestone) {
2995
- if (line.match(/^###\s+Milestone/)) {
2996
- milestones.push(currentMilestone);
2997
- currentMilestone = null;
2998
- continue;
2999
- }
3000
- const todoMatch = line.match(/^-\s+\[([ xX])\]\s*(.+)/);
3001
- if (todoMatch) {
3002
- const isCompleted = todoMatch[1].toLowerCase() === "x";
3003
- if (isCompleted) {
3004
- currentMilestone.completedCount++;
3005
- }
3006
- currentMilestone.todos.push(todoMatch[2].trim());
3007
- }
3008
- }
3009
- }
3010
- if (currentMilestone) {
3011
- milestones.push(currentMilestone);
3012
- }
3013
- return milestones;
3014
- }
3015
- function parseTodos(spec, milestoneTitle) {
3016
- const lines = spec.split("\n");
3017
- const todos = [];
3018
- let inTargetMilestone = false;
3019
- for (const line of lines) {
3020
- if (line.includes(milestoneTitle)) {
3021
- inTargetMilestone = true;
3022
- continue;
3023
- }
3024
- if (inTargetMilestone) {
3025
- if (line.match(/^###\s+Milestone/)) {
3026
- break;
3027
- }
3028
- const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
3029
- if (todoMatch) {
3030
- todos.push(todoMatch[1].trim());
3031
- }
3032
- }
3033
- }
3034
- return todos;
3035
- }
3036
3053
  function topologicalSort(specs) {
3037
3054
  const sorted = [];
3038
3055
  const visited = /* @__PURE__ */ new Set();
@@ -3190,7 +3207,19 @@ async function askAndUpdateSpecStatus(specFile, milestone, todo) {
3190
3207
  logger.warn("\u672A\u627E\u5230\u5BF9\u5E94\u7684 todo \u9879");
3191
3208
  return;
3192
3209
  }
3193
- await FileUtils.write(specFile, lines.join("\n"));
3210
+ const newContent = lines.join("\n");
3211
+ const computedStatus = SpecUtils.parseSpecStatus(newContent);
3212
+ let finalLines = lines;
3213
+ const statusLineIndex = finalLines.findIndex((l) => l.match(/状态.*[::]/));
3214
+ if (statusLineIndex !== -1) {
3215
+ const oldLine = finalLines[statusLineIndex];
3216
+ const newLine = oldLine.replace(/([::]\s*)(.+)/, `$1**${computedStatus}**`);
3217
+ if (oldLine !== newLine) {
3218
+ finalLines[statusLineIndex] = newLine;
3219
+ logger.info(`\u81EA\u52A8\u540C\u6B65 Spec \u5934\u90E8\u72B6\u6001\u4E3A: ${computedStatus}`);
3220
+ }
3221
+ }
3222
+ await FileUtils.write(specFile, finalLines.join("\n"));
3194
3223
  logger.success("Spec \u6587\u4EF6\u5DF2\u66F4\u65B0");
3195
3224
  } catch (error) {
3196
3225
  logger.warn(`\u66F4\u65B0 spec \u6587\u4EF6\u5931\u8D25: ${error}`);
@@ -4610,11 +4639,12 @@ async function displayFeatureInventory() {
4610
4639
  for (const file of specs) {
4611
4640
  const filePath = path13.join(specDir, file);
4612
4641
  const content = await FileUtils.read(filePath);
4613
- const status = parseSpecStatus2(content);
4642
+ const status = SpecUtils.parseSpecStatus(content);
4643
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
4614
4644
  inventory.push({
4615
4645
  name: file.replace(".md", ""),
4616
- status,
4617
- progress: getProgress(content)
4646
+ status: statusWithIcon,
4647
+ progress: SpecUtils.getProgress(content)
4618
4648
  });
4619
4649
  }
4620
4650
  const tableData = inventory.map((item) => [
@@ -4681,28 +4711,6 @@ async function displayRecentActivity() {
4681
4711
  logger.newLine();
4682
4712
  }
4683
4713
  }
4684
- function parseSpecStatus2(spec) {
4685
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
4686
- if (statusMatch) {
4687
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
4688
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2713 \u5DF2\u5B8C\u6210";
4689
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
4690
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
4691
- }
4692
- return "\u25CB \u672A\u5F00\u59CB";
4693
- }
4694
- function getProgress(spec) {
4695
- const milestoneMatches = spec.match(/###\s+Milestone\s+\d+:/g);
4696
- const milestones = milestoneMatches ? milestoneMatches.length : 0;
4697
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
4698
- const totalTodos = todoMatches ? todoMatches.length : 0;
4699
- const completedMatches = spec.match(/-\s+\[x\]/g);
4700
- const completedTodos = completedMatches ? completedMatches.length : 0;
4701
- if (totalTodos === 0) {
4702
- return "-";
4703
- }
4704
- return `${completedTodos}/${totalTodos}`;
4705
- }
4706
4714
  var statusCommand;
4707
4715
  var init_status = __esm({
4708
4716
  "src/commands/status.ts"() {
@@ -5024,16 +5032,17 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
5024
5032
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5025
5033
  const specPath = path15.join(specsDir, specFile);
5026
5034
  const content = await FileUtils.read(specPath);
5027
- const status = parseSpecStatus3(content);
5028
- const progress = getSpecProgress(content);
5035
+ const status = SpecUtils.parseSpecStatus(content);
5036
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
5037
+ const progress = SpecUtils.getProgress(content);
5029
5038
  let completionDate = "-";
5030
- if (status === "\u2705 \u5DF2\u5B8C\u6210") {
5039
+ if (status === "\u5DF2\u5B8C\u6210") {
5031
5040
  const dateMatch = content.match(/完成日期.*[::]\s*(.+)/);
5032
5041
  if (dateMatch) {
5033
5042
  completionDate = dateMatch[1].trim();
5034
5043
  }
5035
5044
  }
5036
- lines.push(`| ${displayName} | ${name}.md | ${status} | ${progress} | ${completionDate} | |`);
5045
+ lines.push(`| ${displayName} | ${name}.md | ${statusWithIcon} | ${progress} | ${completionDate} | |`);
5037
5046
  }
5038
5047
  const newContent = lines.join("\n") + "\n";
5039
5048
  await replaceOrInsertSection(aiMemoryFile, "## \u529F\u80FD\u6E05\u5355", newContent);
@@ -5220,26 +5229,6 @@ async function replaceOrInsertSection(aiMemoryFile, sectionTitle, newContent) {
5220
5229
  await FileUtils.write(aiMemoryFile, content.trimEnd() + "\n\n" + newContent + "\n");
5221
5230
  }
5222
5231
  }
5223
- function parseSpecStatus3(spec) {
5224
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
5225
- if (statusMatch) {
5226
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
5227
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2705 \u5DF2\u5B8C\u6210";
5228
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
5229
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
5230
- }
5231
- return "\u25CB \u672A\u5F00\u59CB";
5232
- }
5233
- function getSpecProgress(spec) {
5234
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
5235
- const totalTodos = todoMatches ? todoMatches.length : 0;
5236
- const completedMatches = spec.match(/-\s+\[x\]/g);
5237
- const completedTodos = completedMatches ? completedMatches.length : 0;
5238
- if (totalTodos === 0) {
5239
- return "-";
5240
- }
5241
- return `${completedTodos}/${totalTodos}`;
5242
- }
5243
5232
  async function syncTemplateVersions(aiMemoryFile, projectDir) {
5244
5233
  logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
5245
5234
  const config = await readTemplateConfig(projectDir);