yg-team-cli 2.5.9 → 2.6.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/README.md CHANGED
@@ -73,6 +73,12 @@
73
73
  - **改进**: `dev` 模式列表现在基于 Milestone 实际完成百分比动态显示 Spec 状态(即使文件头部状态未及时手动更新)
74
74
  - **修复**: 在通过 `dev` 命令完成任务时,系统现在会自动同步并更新 Spec 文件头部的“状态”标识,确保文档与进度一致
75
75
 
76
+ **v2.6.0** - 统一状态解析逻辑与多维同步
77
+ - **重构**: 将核心 Spec 解析逻辑沉淀至 `SpecUtils` 共享工具类,实现全系统逻辑收口
78
+ - **改进**: `status` 命令与 `sync-memory` 命令现在统一接入动态进度推导引擎,解决状态显示落后的问题
79
+ - **优化**: 全面统一了各命令中状态图标(✓, ⟳, ◉, ○)的定义,提供一致的视觉交互体验
80
+ - **增强**: `sync-memory` 现在能更准确地根据任务勾选情况自动更新 `AI_MEMORY.md` 中的完成日期及状态列
81
+
76
82
  **v2.4.10** - 修复 Claude 返回权限确认而非实际内容问题
77
83
  - **修复**: 添加 `--dangerously-skip-permissions` 参数跳过权限确认
78
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,26 +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 milestones = parseMilestones(spec);
2949
- if (milestones.length > 0) {
2950
- const totalTodos = milestones.reduce((sum, m) => sum + m.todos.length, 0);
2951
- const completedTodos = milestones.reduce((sum, m) => sum + m.completedCount, 0);
2952
- if (totalTodos > 0) {
2953
- if (completedTodos === totalTodos) return "\u5DF2\u5B8C\u6210";
2954
- if (completedTodos > 0) return "\u8FDB\u884C\u4E2D";
2955
- }
2956
- return "\u5DF2\u62C6\u5206";
2957
- }
2958
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
2959
- if (statusMatch) {
2960
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
2961
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u5DF2\u5B8C\u6210";
2962
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u8FDB\u884C\u4E2D";
2963
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u5DF2\u62C6\u5206";
2964
- }
2965
- return "\u672A\u5F00\u59CB";
2966
- }
2967
3031
  function parseDependencies(spec) {
2968
3032
  const deps = [];
2969
3033
  let inDepsSection = false;
@@ -2986,63 +3050,6 @@ function parseDependencies(spec) {
2986
3050
  }
2987
3051
  return deps;
2988
3052
  }
2989
- function parseMilestones(spec) {
2990
- const milestones = [];
2991
- const lines = spec.split("\n");
2992
- let currentMilestone = null;
2993
- let inMilestone = false;
2994
- for (const line of lines) {
2995
- if (line.match(/^###\s+Milestone\s+\d+:/)) {
2996
- if (currentMilestone) {
2997
- milestones.push(currentMilestone);
2998
- }
2999
- const title = line.replace(/^###\s+/, "").trim();
3000
- currentMilestone = { title, todos: [], completedCount: 0 };
3001
- inMilestone = true;
3002
- continue;
3003
- }
3004
- if (inMilestone && currentMilestone) {
3005
- if (line.match(/^###\s+Milestone/)) {
3006
- milestones.push(currentMilestone);
3007
- currentMilestone = null;
3008
- continue;
3009
- }
3010
- const todoMatch = line.match(/^-\s+\[([ xX])\]\s*(.+)/);
3011
- if (todoMatch) {
3012
- const isCompleted = todoMatch[1].toLowerCase() === "x";
3013
- if (isCompleted) {
3014
- currentMilestone.completedCount++;
3015
- }
3016
- currentMilestone.todos.push(todoMatch[2].trim());
3017
- }
3018
- }
3019
- }
3020
- if (currentMilestone) {
3021
- milestones.push(currentMilestone);
3022
- }
3023
- return milestones;
3024
- }
3025
- function parseTodos(spec, milestoneTitle) {
3026
- const lines = spec.split("\n");
3027
- const todos = [];
3028
- let inTargetMilestone = false;
3029
- for (const line of lines) {
3030
- if (line.includes(milestoneTitle)) {
3031
- inTargetMilestone = true;
3032
- continue;
3033
- }
3034
- if (inTargetMilestone) {
3035
- if (line.match(/^###\s+Milestone/)) {
3036
- break;
3037
- }
3038
- const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
3039
- if (todoMatch) {
3040
- todos.push(todoMatch[1].trim());
3041
- }
3042
- }
3043
- }
3044
- return todos;
3045
- }
3046
3053
  function topologicalSort(specs) {
3047
3054
  const sorted = [];
3048
3055
  const visited = /* @__PURE__ */ new Set();
@@ -3201,7 +3208,7 @@ async function askAndUpdateSpecStatus(specFile, milestone, todo) {
3201
3208
  return;
3202
3209
  }
3203
3210
  const newContent = lines.join("\n");
3204
- const computedStatus = parseSpecStatus(newContent);
3211
+ const computedStatus = SpecUtils.parseSpecStatus(newContent);
3205
3212
  let finalLines = lines;
3206
3213
  const statusLineIndex = finalLines.findIndex((l) => l.match(/状态.*[::]/));
3207
3214
  if (statusLineIndex !== -1) {
@@ -4632,11 +4639,12 @@ async function displayFeatureInventory() {
4632
4639
  for (const file of specs) {
4633
4640
  const filePath = path13.join(specDir, file);
4634
4641
  const content = await FileUtils.read(filePath);
4635
- const status = parseSpecStatus2(content);
4642
+ const status = SpecUtils.parseSpecStatus(content);
4643
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
4636
4644
  inventory.push({
4637
4645
  name: file.replace(".md", ""),
4638
- status,
4639
- progress: getProgress(content)
4646
+ status: statusWithIcon,
4647
+ progress: SpecUtils.getProgress(content)
4640
4648
  });
4641
4649
  }
4642
4650
  const tableData = inventory.map((item) => [
@@ -4646,9 +4654,9 @@ async function displayFeatureInventory() {
4646
4654
  ]);
4647
4655
  logger.table(["\u529F\u80FD", "\u72B6\u6001", "\u8FDB\u5EA6"], tableData);
4648
4656
  logger.newLine();
4649
- const completed = inventory.filter((i) => i.status === "\u5DF2\u5B8C\u6210").length;
4650
- const inProgress = inventory.filter((i) => i.status === "\u8FDB\u884C\u4E2D").length;
4651
- const pending = inventory.filter((i) => i.status === "\u672A\u5F00\u59CB").length;
4657
+ const completed = inventory.filter((i) => i.status.includes("\u5DF2\u5B8C\u6210")).length;
4658
+ const inProgress = inventory.filter((i) => i.status.includes("\u8FDB\u884C\u4E2D")).length;
4659
+ const pending = inventory.filter((i) => i.status.includes("\u672A\u5F00\u59CB")).length;
4652
4660
  logger.info(`\u603B\u8BA1: ${inventory.length} | \u5DF2\u5B8C\u6210: ${completed} | \u8FDB\u884C\u4E2D: ${inProgress} | \u672A\u5F00\u59CB: ${pending}`);
4653
4661
  logger.newLine();
4654
4662
  }
@@ -4703,28 +4711,6 @@ async function displayRecentActivity() {
4703
4711
  logger.newLine();
4704
4712
  }
4705
4713
  }
4706
- function parseSpecStatus2(spec) {
4707
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
4708
- if (statusMatch) {
4709
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
4710
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2713 \u5DF2\u5B8C\u6210";
4711
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
4712
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
4713
- }
4714
- return "\u25CB \u672A\u5F00\u59CB";
4715
- }
4716
- function getProgress(spec) {
4717
- const milestoneMatches = spec.match(/###\s+Milestone\s+\d+:/g);
4718
- const milestones = milestoneMatches ? milestoneMatches.length : 0;
4719
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
4720
- const totalTodos = todoMatches ? todoMatches.length : 0;
4721
- const completedMatches = spec.match(/-\s+\[x\]/g);
4722
- const completedTodos = completedMatches ? completedMatches.length : 0;
4723
- if (totalTodos === 0) {
4724
- return "-";
4725
- }
4726
- return `${completedTodos}/${totalTodos}`;
4727
- }
4728
4714
  var statusCommand;
4729
4715
  var init_status = __esm({
4730
4716
  "src/commands/status.ts"() {
@@ -5046,16 +5032,17 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
5046
5032
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5047
5033
  const specPath = path15.join(specsDir, specFile);
5048
5034
  const content = await FileUtils.read(specPath);
5049
- const status = parseSpecStatus3(content);
5050
- const progress = getSpecProgress(content);
5035
+ const status = SpecUtils.parseSpecStatus(content);
5036
+ const statusWithIcon = SpecUtils.getStatusWithIcon(status);
5037
+ const progress = SpecUtils.getProgress(content);
5051
5038
  let completionDate = "-";
5052
- if (status === "\u2705 \u5DF2\u5B8C\u6210") {
5039
+ if (status === "\u5DF2\u5B8C\u6210") {
5053
5040
  const dateMatch = content.match(/完成日期.*[::]\s*(.+)/);
5054
5041
  if (dateMatch) {
5055
5042
  completionDate = dateMatch[1].trim();
5056
5043
  }
5057
5044
  }
5058
- lines.push(`| ${displayName} | ${name}.md | ${status} | ${progress} | ${completionDate} | |`);
5045
+ lines.push(`| ${displayName} | ${name}.md | ${statusWithIcon} | ${progress} | ${completionDate} | |`);
5059
5046
  }
5060
5047
  const newContent = lines.join("\n") + "\n";
5061
5048
  await replaceOrInsertSection(aiMemoryFile, "## \u529F\u80FD\u6E05\u5355", newContent);
@@ -5242,26 +5229,6 @@ async function replaceOrInsertSection(aiMemoryFile, sectionTitle, newContent) {
5242
5229
  await FileUtils.write(aiMemoryFile, content.trimEnd() + "\n\n" + newContent + "\n");
5243
5230
  }
5244
5231
  }
5245
- function parseSpecStatus3(spec) {
5246
- const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
5247
- if (statusMatch) {
5248
- const status = statusMatch[1].replace(/\*\*/g, "").trim();
5249
- if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2705 \u5DF2\u5B8C\u6210";
5250
- if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
5251
- if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
5252
- }
5253
- return "\u25CB \u672A\u5F00\u59CB";
5254
- }
5255
- function getSpecProgress(spec) {
5256
- const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
5257
- const totalTodos = todoMatches ? todoMatches.length : 0;
5258
- const completedMatches = spec.match(/-\s+\[x\]/g);
5259
- const completedTodos = completedMatches ? completedMatches.length : 0;
5260
- if (totalTodos === 0) {
5261
- return "-";
5262
- }
5263
- return `${completedTodos}/${totalTodos}`;
5264
- }
5265
5232
  async function syncTemplateVersions(aiMemoryFile, projectDir) {
5266
5233
  logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
5267
5234
  const config = await readTemplateConfig(projectDir);