yg-team-cli 2.6.2 → 2.6.3

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
@@ -460,6 +460,121 @@ var init_utils = __esm({
460
460
  }
461
461
  return milestones;
462
462
  }
463
+ /**
464
+ * 解析增强版里程碑信息(支持 [F]/[B]/[I] 前缀分类)
465
+ */
466
+ static parseMilestonesEnhanced(content) {
467
+ const milestones = [];
468
+ const lines = content.split("\n");
469
+ let currentMilestone = null;
470
+ let inMilestone = false;
471
+ for (const line of lines) {
472
+ if (line.match(/^###\s+Milestone\s+\d+:/)) {
473
+ if (currentMilestone) {
474
+ currentMilestone.totalCount = currentMilestone.todos.length;
475
+ milestones.push(currentMilestone);
476
+ }
477
+ const title = line.replace(/^###\s+/, "").trim();
478
+ currentMilestone = {
479
+ title,
480
+ todos: [],
481
+ frontendTodos: [],
482
+ backendTodos: [],
483
+ integrationTodos: [],
484
+ generalTodos: [],
485
+ completedCount: 0,
486
+ totalCount: 0
487
+ };
488
+ inMilestone = true;
489
+ continue;
490
+ }
491
+ if (inMilestone && currentMilestone) {
492
+ if (line.match(/^###\s+Milestone/)) {
493
+ currentMilestone.totalCount = currentMilestone.todos.length;
494
+ milestones.push(currentMilestone);
495
+ currentMilestone = null;
496
+ const title = line.replace(/^###\s+/, "").trim();
497
+ currentMilestone = {
498
+ title,
499
+ todos: [],
500
+ frontendTodos: [],
501
+ backendTodos: [],
502
+ integrationTodos: [],
503
+ generalTodos: [],
504
+ completedCount: 0,
505
+ totalCount: 0
506
+ };
507
+ continue;
508
+ }
509
+ const todoMatch = line.match(/^-\s+\[([ xX])\]\s*(\[[FBIN]\])?\s*(.+)/);
510
+ if (todoMatch) {
511
+ const isCompleted = todoMatch[1].toLowerCase() === "x";
512
+ const prefix = todoMatch[2]?.trim() || "";
513
+ const rawContent = todoMatch[3].trim();
514
+ let type = "general";
515
+ let apiAssociation;
516
+ let dependencies;
517
+ if (prefix === "[F]") {
518
+ type = "frontend";
519
+ } else if (prefix === "[B]") {
520
+ type = "backend";
521
+ const apiMatch = rawContent.match(/\(关联 API:\s*`([^`]+)`\)/);
522
+ if (apiMatch) {
523
+ apiAssociation = apiMatch[1];
524
+ }
525
+ } else if (prefix === "[I]") {
526
+ type = "integration";
527
+ const depMatch = rawContent.match(/\(依赖:\s*([^)]+)\)/);
528
+ if (depMatch) {
529
+ dependencies = depMatch[1].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
530
+ }
531
+ }
532
+ const todoItem = {
533
+ raw: line.trim(),
534
+ content: rawContent.replace(/\s*\(关联 API:\s*`[^`]+`\)/, "").replace(/\s*\(依赖:\s*[^)]+\)/, "").trim(),
535
+ type,
536
+ apiAssociation,
537
+ dependencies,
538
+ isCompleted
539
+ };
540
+ currentMilestone.todos.push(todoItem);
541
+ if (isCompleted) {
542
+ currentMilestone.completedCount++;
543
+ }
544
+ switch (type) {
545
+ case "frontend":
546
+ currentMilestone.frontendTodos.push(todoItem);
547
+ break;
548
+ case "backend":
549
+ currentMilestone.backendTodos.push(todoItem);
550
+ break;
551
+ case "integration":
552
+ currentMilestone.integrationTodos.push(todoItem);
553
+ break;
554
+ default:
555
+ currentMilestone.generalTodos.push(todoItem);
556
+ }
557
+ }
558
+ }
559
+ }
560
+ if (currentMilestone) {
561
+ currentMilestone.totalCount = currentMilestone.todos.length;
562
+ milestones.push(currentMilestone);
563
+ }
564
+ return milestones;
565
+ }
566
+ /**
567
+ * 解析增强版里程碑(向后兼容版 - 优先尝试增强解析,失败回退到简单解析)
568
+ */
569
+ static parseMilestonesEnhancedWithFallback(content) {
570
+ const enhanced = this.parseMilestonesEnhanced(content);
571
+ const hasEnhancedFormat = enhanced.some(
572
+ (m) => m.todos.some(
573
+ (t) => t.type !== "general" || t.apiAssociation || t.dependencies
574
+ )
575
+ );
576
+ return { enhanced, isEnhanced: hasEnhancedFormat };
577
+ }
463
578
  /**
464
579
  * 解析 spec 状态(基于进度动态推导)
465
580
  */
@@ -886,22 +1001,22 @@ var init_gitlab_api = __esm({
886
1001
  * 从 Git URL 中提取项目路径
887
1002
  */
888
1003
  static parseProjectPath(repository) {
889
- let path20 = repository;
890
- if (path20.startsWith("git@")) {
891
- path20 = path20.replace(/^git@/, "");
892
- const colonIndex = path20.indexOf(":");
1004
+ let path21 = repository;
1005
+ if (path21.startsWith("git@")) {
1006
+ path21 = path21.replace(/^git@/, "");
1007
+ const colonIndex = path21.indexOf(":");
893
1008
  if (colonIndex !== -1) {
894
- path20 = path20.substring(colonIndex + 1);
1009
+ path21 = path21.substring(colonIndex + 1);
895
1010
  }
896
1011
  } else {
897
- path20 = path20.replace(/^https?:\/\//, "");
898
- const parts = path20.split("/");
1012
+ path21 = path21.replace(/^https?:\/\//, "");
1013
+ const parts = path21.split("/");
899
1014
  if (parts.length > 1) {
900
- path20 = parts.slice(1).join("/");
1015
+ path21 = parts.slice(1).join("/");
901
1016
  }
902
1017
  }
903
- path20 = path20.replace(/\.git$/, "");
904
- return path20;
1018
+ path21 = path21.replace(/\.git$/, "");
1019
+ return path21;
905
1020
  }
906
1021
  /**
907
1022
  * 编码项目路径用于 API 请求
@@ -955,10 +1070,10 @@ var init_gitlab_api = __esm({
955
1070
  // src/index.ts
956
1071
  init_esm_shims();
957
1072
  init_logger();
958
- import { Command as Command16 } from "commander";
1073
+ import { Command as Command17 } from "commander";
959
1074
  import chalk4 from "chalk";
960
1075
  import fs5 from "fs-extra";
961
- import path19 from "path";
1076
+ import path20 from "path";
962
1077
  import { fileURLToPath as fileURLToPath2 } from "url";
963
1078
 
964
1079
  // src/commands/init.ts
@@ -2768,7 +2883,7 @@ ${cleanedMilestones}`;
2768
2883
  function buildBreakdownPrompt(specContent) {
2769
2884
  return `Role: Senior Technical Lead and Agile Coach
2770
2885
 
2771
- Task: Break down the following feature spec into milestones and todo lists.
2886
+ Task: Break down the following feature spec into milestones with frontend, backend, and integration tasks.
2772
2887
 
2773
2888
  Context:
2774
2889
  - Read TECH_STACK.md for technology constraints
@@ -2784,13 +2899,18 @@ ${specContent}
2784
2899
  Output Requirements:
2785
2900
  1. Parse the existing spec content and identify technical requirements.
2786
2901
  2. Break it down into 2-5 milestones.
2787
- 3. Each milestone should have:
2788
- - Clear name and objective
2789
- - Estimated days (1-3 days per milestone)
2790
- - Todo list with 3-8 actionable items
2902
+ 3. Each milestone must have three sections:
2903
+ - **\u524D\u7AEF\u4EFB\u52A1**: Frontend tasks marked with [F] prefix
2904
+ - **\u540E\u7AEF\u4EFB\u52A1**: Backend tasks marked with [B] prefix, include API associations
2905
+ - **\u8054\u8C03\u4EFB\u52A1**: Integration tasks marked with [I] prefix, include dependencies
2791
2906
  4. Todo items should be:
2792
- - Concrete, specific, and testable.
2793
- - Independent as much as possible.
2907
+ - Concrete, specific, and testable
2908
+ - Independent as much as possible
2909
+
2910
+ Task Prefix Format:
2911
+ - Frontend: \`- [ ] [F] \u5177\u4F53\u7684\u524D\u7AEF\u4EFB\u52A1\u63CF\u8FF0\`
2912
+ - Backend: \`- [ ] [B] \u5177\u4F53\u7684\u540E\u7AEF\u4EFB\u52A1\u63CF\u8FF0 (\u5173\u8054 API: \`METHOD /api/path\`)\`
2913
+ - Integration: \`- [ ] [I] \u8054\u8C03\u4EFB\u52A1\u63CF\u8FF0 (\u4F9D\u8D56: F-x, B-y)\`
2794
2914
 
2795
2915
  IMPORTANT:
2796
2916
  - Output ONLY the "## \u91CC\u7A0B\u7891 (Milestones)" section.
@@ -2805,9 +2925,16 @@ IMPORTANT:
2805
2925
  **\u76EE\u6807**: [\u7B80\u77ED\u63CF\u8FF0\u8FD9\u4E2A\u91CC\u7A0B\u7891\u7684\u76EE\u6807]
2806
2926
  **\u9884\u4F30**: 2 \u5929
2807
2927
 
2808
- - [ ] Todo 1 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
2809
- - [ ] Todo 2 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
2810
- - [ ] Todo 3 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
2928
+ **\u524D\u7AEF\u4EFB\u52A1**:
2929
+ - [ ] [F] \u524D\u7AEF\u4EFB\u52A1\u63CF\u8FF0 1
2930
+ - [ ] [F] \u524D\u7AEF\u4EFB\u52A1\u63CF\u8FF0 2
2931
+
2932
+ **\u540E\u7AEF\u4EFB\u52A1**:
2933
+ - [ ] [B] \u540E\u7AEF\u4EFB\u52A1\u63CF\u8FF0 1 (\u5173\u8054 API: \`POST /api/xxx\`)
2934
+ - [ ] [B] \u540E\u7AEF\u4EFB\u52A1\u63CF\u8FF0 2 (\u5173\u8054 API: \`GET /api/yyy\`)
2935
+
2936
+ **\u8054\u8C03\u4EFB\u52A1**:
2937
+ - [ ] [I] \u8054\u8C03\u4EFB\u52A1\u63CF\u8FF0 1 (\u4F9D\u8D56: F-1, B-1)
2811
2938
 
2812
2939
  ### Milestone 2: [\u91CC\u7A0B\u7891\u540D\u79F0]
2813
2940
  ...
@@ -2910,8 +3037,8 @@ async function selectMilestone(specFile) {
2910
3037
  logger.step("\u6B65\u9AA4 2/3: \u89E3\u6790 milestones...");
2911
3038
  logger.newLine();
2912
3039
  const specContent = await FileUtils.read(specFile);
2913
- const milestones = SpecUtils.parseMilestones(specContent);
2914
- if (milestones.length === 0) {
3040
+ const { enhanced, isEnhanced } = SpecUtils.parseMilestonesEnhancedWithFallback(specContent);
3041
+ if (enhanced.length === 0) {
2915
3042
  logger.info("\u8BE5 spec \u5C1A\u672A\u62C6\u5206 milestones");
2916
3043
  const { breakdownNow } = await inquirer3.prompt([
2917
3044
  {
@@ -2929,19 +3056,32 @@ async function selectMilestone(specFile) {
2929
3056
  return "\u6574\u4E2A spec";
2930
3057
  }
2931
3058
  }
2932
- const choices = milestones.map((m, idx) => {
2933
- const isDone = m.completedCount === m.todos.length && m.todos.length > 0;
3059
+ const choices = enhanced.map((m, idx) => {
3060
+ const isDone = m.completedCount === m.totalCount && m.totalCount > 0;
2934
3061
  const statusIcon = isDone ? "\u2713" : m.completedCount > 0 ? "\u27F3" : "\u25CB";
2935
- const progress = `[${m.completedCount}/${m.todos.length}]`;
3062
+ const progress = `[${m.completedCount}/${m.totalCount}]`;
2936
3063
  const label = isDone ? `\u2713 ${m.title}` : `${statusIcon} ${progress} ${m.title}`;
3064
+ let detailInfo = `(${m.totalCount} \u4E2A\u4EFB\u52A1)`;
3065
+ if (isEnhanced) {
3066
+ const fCount = m.frontendTodos.filter((t) => !t.isCompleted).length;
3067
+ const bCount = m.backendTodos.filter((t) => !t.isCompleted).length;
3068
+ const iCount = m.integrationTodos.filter((t) => !t.isCompleted).length;
3069
+ const details = [];
3070
+ if (fCount > 0) details.push(`\u524D\u7AEF:${fCount}`);
3071
+ if (bCount > 0) details.push(`\u540E\u7AEF:${bCount}`);
3072
+ if (iCount > 0) details.push(`\u8054\u8C03:${iCount}`);
3073
+ if (details.length > 0) {
3074
+ detailInfo = `(${details.join(", ")} \u672A\u5B8C\u6210)`;
3075
+ }
3076
+ }
2937
3077
  return {
2938
- name: `${idx + 1}. ${label} (${m.todos.length} \u4E2A\u4EFB\u52A1)`,
3078
+ name: `${idx + 1}. ${label} ${detailInfo}`,
2939
3079
  value: m.title,
2940
3080
  short: m.title
2941
3081
  };
2942
3082
  });
2943
3083
  choices.push({
2944
- name: `${milestones.length + 1}. \u6574\u4E2A spec (\u5168\u90E8 milestones)`,
3084
+ name: `${enhanced.length + 1}. \u6574\u4E2A spec (\u5168\u90E8 milestones)`,
2945
3085
  value: "\u6574\u4E2A spec",
2946
3086
  short: "\u6574\u4E2A spec"
2947
3087
  });
@@ -2962,6 +3102,79 @@ async function selectTodo(specFile, milestone) {
2962
3102
  logger.newLine();
2963
3103
  logger.step("\u6B65\u9AA4 3/3: \u9009\u62E9 todo \u4EFB\u52A1...");
2964
3104
  logger.newLine();
3105
+ const specContent = await FileUtils.read(specFile);
3106
+ const { enhanced, isEnhanced } = SpecUtils.parseMilestonesEnhancedWithFallback(specContent);
3107
+ const targetMilestone = enhanced.find((m) => m.title === milestone);
3108
+ if (!targetMilestone || !isEnhanced) {
3109
+ return await selectTodoSimple(specFile, milestone);
3110
+ }
3111
+ return await selectTodoEnhanced(targetMilestone);
3112
+ }
3113
+ async function selectTodoEnhanced(milestone) {
3114
+ const choices = [];
3115
+ if (milestone.frontendTodos.length > 0) {
3116
+ choices.push({ name: `\u2501\u2501 \u524D\u7AEF\u4EFB\u52A1 (${milestone.frontendTodos.length}) \u2501\u2501`, disabled: true });
3117
+ milestone.frontendTodos.forEach((todo2, idx) => {
3118
+ const icon = todo2.isCompleted ? "[x]" : "[ ]";
3119
+ choices.push({
3120
+ name: ` [F] ${icon} ${todo2.content}`,
3121
+ value: todo2.content,
3122
+ short: `[F] ${todo2.content}`
3123
+ });
3124
+ });
3125
+ }
3126
+ if (milestone.backendTodos.length > 0) {
3127
+ choices.push({ name: `\u2501\u2501 \u540E\u7AEF\u4EFB\u52A1 (${milestone.backendTodos.length}) \u2501\u2501`, disabled: true });
3128
+ milestone.backendTodos.forEach((todo2, idx) => {
3129
+ const icon = todo2.isCompleted ? "[x]" : "[ ]";
3130
+ const apiInfo = todo2.apiAssociation ? ` (${todo2.apiAssociation})` : "";
3131
+ choices.push({
3132
+ name: ` [B] ${icon} ${todo2.content}${apiInfo}`,
3133
+ value: todo2.content,
3134
+ short: `[B] ${todo2.content}`
3135
+ });
3136
+ });
3137
+ }
3138
+ if (milestone.integrationTodos.length > 0) {
3139
+ choices.push({ name: `\u2501\u2501 \u8054\u8C03\u4EFB\u52A1 (${milestone.integrationTodos.length}) \u2501\u2501`, disabled: true });
3140
+ milestone.integrationTodos.forEach((todo2, idx) => {
3141
+ const icon = todo2.isCompleted ? "[x]" : "[ ]";
3142
+ const depsInfo = todo2.dependencies ? ` (\u4F9D\u8D56: ${todo2.dependencies.join(", ")})` : "";
3143
+ choices.push({
3144
+ name: ` [I] ${icon} ${todo2.content}${depsInfo}`,
3145
+ value: todo2.content,
3146
+ short: `[I] ${todo2.content}`
3147
+ });
3148
+ });
3149
+ }
3150
+ if (milestone.generalTodos.length > 0) {
3151
+ choices.push({ name: `\u2501\u2501 \u901A\u7528\u4EFB\u52A1 (${milestone.generalTodos.length}) \u2501\u2501`, disabled: true });
3152
+ milestone.generalTodos.forEach((todo2, idx) => {
3153
+ const icon = todo2.isCompleted ? "[x]" : "[ ]";
3154
+ choices.push({
3155
+ name: ` ${icon} ${todo2.content}`,
3156
+ value: todo2.content,
3157
+ short: todo2.content
3158
+ });
3159
+ });
3160
+ }
3161
+ choices.push({ name: "\u2501\u2501 \u5176\u4ED6 \u2501\u2501", disabled: true });
3162
+ choices.push({
3163
+ name: `\u5168\u90E8\u4EFB\u52A1 (\u6574\u4E2A milestone)`,
3164
+ value: "\u5168\u90E8\u4EFB\u52A1",
3165
+ short: "\u5168\u90E8\u4EFB\u52A1"
3166
+ });
3167
+ const { todo } = await inquirer3.prompt([
3168
+ {
3169
+ type: "list",
3170
+ name: "todo",
3171
+ message: "\u9009\u62E9 todo \u4EFB\u52A1:",
3172
+ choices
3173
+ }
3174
+ ]);
3175
+ return todo;
3176
+ }
3177
+ async function selectTodoSimple(specFile, milestone) {
2965
3178
  const specContent = await FileUtils.read(specFile);
2966
3179
  const milestones = SpecUtils.parseMilestones(specContent);
2967
3180
  const targetMilestone = milestones.find((m) => m.title === milestone);
@@ -3189,10 +3402,14 @@ async function askAndUpdateSpecStatus(specFile, milestone, todo) {
3189
3402
  break;
3190
3403
  }
3191
3404
  if (todo !== "\u5168\u90E8\u529F\u80FD" && todo !== "\u5168\u90E8\u4EFB\u52A1") {
3192
- const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
3193
- if (todoMatch && todoMatch[1].trim() === todo) {
3194
- targetTodoIndex = i;
3195
- break;
3405
+ const todoMatch = line.match(/^-\s+\[ \]\s*(\[[FBIN]\]\s*)?(.+)/);
3406
+ if (todoMatch) {
3407
+ const todoContent = todoMatch[2].trim();
3408
+ const cleanContent = todoContent.replace(/\s*\(关联 API:\s*`[^`]+`\)/, "").replace(/\s*\(依赖:\s*[^)]+\)/, "").trim();
3409
+ if (cleanContent === todo) {
3410
+ targetTodoIndex = i;
3411
+ break;
3412
+ }
3196
3413
  }
3197
3414
  }
3198
3415
  }
@@ -4419,13 +4636,596 @@ git checkout ${data.branch}
4419
4636
  `;
4420
4637
  }
4421
4638
 
4422
- // src/commands/lint.ts
4639
+ // src/commands/accept.ts
4423
4640
  init_esm_shims();
4424
4641
  init_utils();
4425
4642
  init_logger();
4426
4643
  import { Command as Command7 } from "commander";
4644
+ import inquirer6 from "inquirer";
4645
+ import path13 from "path";
4646
+ var acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").action(async (specFile) => {
4647
+ try {
4648
+ logger.header("\u9A8C\u6536\u6A21\u5F0F");
4649
+ logger.newLine();
4650
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
4651
+ if (!hasTechStack) {
4652
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
4653
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
4654
+ process.exit(1);
4655
+ }
4656
+ logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
4657
+ const selectedSpec = await selectSpec2(specFile);
4658
+ const selectedMilestone = await selectMilestone2(selectedSpec);
4659
+ const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
4660
+ await generateAcceptanceReport(result);
4661
+ if (result.issues.length > 0) {
4662
+ await handleIssues(result);
4663
+ }
4664
+ await syncSpecStatus(selectedSpec, result);
4665
+ logger.header("\u9A8C\u6536\u5B8C\u6210!");
4666
+ logger.newLine();
4667
+ } catch (error) {
4668
+ logger.error(`\u9A8C\u6536\u5931\u8D25: ${error.message}`);
4669
+ if (process.env.DEBUG) {
4670
+ console.error(error);
4671
+ }
4672
+ process.exit(1);
4673
+ }
4674
+ });
4675
+ async function selectSpec2(defaultSpec) {
4676
+ logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
4677
+ logger.newLine();
4678
+ const specDir = "docs/specs";
4679
+ const exists = await FileUtils.exists(specDir);
4680
+ if (!exists) {
4681
+ throw new Error("docs/specs \u76EE\u5F55\u4E0D\u5B58\u5728");
4682
+ }
4683
+ const files = await FileUtils.findFiles("*.md", specDir);
4684
+ const specFiles = files.filter((f) => !f.includes("template"));
4685
+ if (specFiles.length === 0) {
4686
+ throw new Error("\u672A\u627E\u5230 spec \u6587\u4EF6");
4687
+ }
4688
+ if (defaultSpec) {
4689
+ const fullPath = defaultSpec.startsWith("docs/specs/") ? defaultSpec : path13.join(specDir, defaultSpec);
4690
+ const exists2 = await FileUtils.exists(fullPath);
4691
+ if (!exists2) {
4692
+ throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${defaultSpec}`);
4693
+ }
4694
+ logger.success(`\u5DF2\u9009\u62E9: ${fullPath}`);
4695
+ return fullPath;
4696
+ }
4697
+ const specs = [];
4698
+ for (const file of specFiles) {
4699
+ const fullPath = path13.join(specDir, file);
4700
+ const content = await FileUtils.read(fullPath);
4701
+ const status = SpecUtils.parseSpecStatus(content);
4702
+ specs.push({ file: fullPath, name: file, status });
4703
+ }
4704
+ const activeSpecs = specs.filter(
4705
+ (s) => s.status === "\u5DF2\u62C6\u5206" || s.status === "\u8FDB\u884C\u4E2D" || s.status === "\u5DF2\u5B8C\u6210"
4706
+ );
4707
+ if (activeSpecs.length === 0) {
4708
+ logger.warn("\u6CA1\u6709\u53EF\u9A8C\u6536\u7684 spec \u6587\u4EF6");
4709
+ const { continueAnyway } = await inquirer6.prompt([
4710
+ {
4711
+ type: "confirm",
4712
+ name: "continueAnyway",
4713
+ message: "\u662F\u5426\u7EE7\u7EED\u67E5\u770B\u6240\u6709 spec?",
4714
+ default: true
4715
+ }
4716
+ ]);
4717
+ if (!continueAnyway) {
4718
+ process.exit(0);
4719
+ }
4720
+ }
4721
+ const targetSpecs = activeSpecs.length > 0 ? activeSpecs : specs;
4722
+ const choices = targetSpecs.map((spec) => ({
4723
+ name: `[${spec.status}] ${spec.name}`,
4724
+ value: spec.file,
4725
+ short: spec.name
4726
+ }));
4727
+ const { selectedFile } = await inquirer6.prompt([
4728
+ {
4729
+ type: "list",
4730
+ name: "selectedFile",
4731
+ message: "\u9009\u62E9\u8981\u9A8C\u6536\u7684 spec:",
4732
+ choices
4733
+ }
4734
+ ]);
4735
+ logger.success(`\u5DF2\u9009\u62E9: ${selectedFile}`);
4736
+ return selectedFile;
4737
+ }
4738
+ async function selectMilestone2(specFile) {
4739
+ logger.newLine();
4740
+ logger.step("\u6B65\u9AA4 2/4: \u89E3\u6790 milestones...");
4741
+ logger.newLine();
4742
+ const specContent = await FileUtils.read(specFile);
4743
+ const { enhanced } = SpecUtils.parseMilestonesEnhancedWithFallback(specContent);
4744
+ if (enhanced.length === 0) {
4745
+ throw new Error("\u8BE5 spec \u5C1A\u672A\u62C6\u5206 milestones\uFF0C\u65E0\u6CD5\u8FDB\u884C\u9A8C\u6536");
4746
+ }
4747
+ const choices = enhanced.map((m, idx) => {
4748
+ const isDone = m.completedCount === m.totalCount;
4749
+ const statusIcon = isDone ? "\u2713" : m.completedCount > 0 ? "\u27F3" : "\u25CB";
4750
+ const progress = `[${m.completedCount}/${m.totalCount}]`;
4751
+ return {
4752
+ name: `${idx + 1}. ${statusIcon} ${progress} ${m.title}`,
4753
+ value: m.title,
4754
+ short: m.title
4755
+ };
4756
+ });
4757
+ const { milestone } = await inquirer6.prompt([
4758
+ {
4759
+ type: "list",
4760
+ name: "milestone",
4761
+ message: "\u9009\u62E9\u8981\u9A8C\u6536\u7684 milestone:",
4762
+ choices
4763
+ }
4764
+ ]);
4765
+ return milestone;
4766
+ }
4767
+ async function runAcceptanceCheck(specFile, milestone) {
4768
+ logger.newLine();
4769
+ logger.step("\u6B65\u9AA4 3/4: \u6267\u884C\u9A8C\u6536\u68C0\u67E5...");
4770
+ logger.newLine();
4771
+ const specContent = await FileUtils.read(specFile);
4772
+ const { enhanced } = SpecUtils.parseMilestonesEnhancedWithFallback(specFile);
4773
+ const targetMilestone = enhanced.find((m) => m.title === milestone);
4774
+ if (!targetMilestone) {
4775
+ throw new Error(`\u672A\u627E\u5230 milestone: ${milestone}`);
4776
+ }
4777
+ const result = {
4778
+ specFile,
4779
+ milestone,
4780
+ checkTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
4781
+ frontendTasks: { total: 0, completed: 0, items: [] },
4782
+ backendTasks: { total: 0, completed: 0, items: [] },
4783
+ apiVerification: { total: 0, verified: 0, items: [] },
4784
+ integrationTasks: { total: 0, completed: 0, items: [] },
4785
+ issues: []
4786
+ };
4787
+ logger.info("\u68C0\u67E5\u524D\u7AEF\u4EFB\u52A1...");
4788
+ for (const todo of targetMilestone.frontendTodos) {
4789
+ const checkResult = await checkFrontendTask(todo);
4790
+ result.frontendTasks.items.push(checkResult);
4791
+ if (checkResult.status === "pass") {
4792
+ result.frontendTasks.completed++;
4793
+ }
4794
+ result.frontendTasks.total++;
4795
+ const icon = checkResult.status === "pass" ? "\u2713" : checkResult.status === "fail" ? "\u2717" : "\u25CB";
4796
+ logger.info(` ${icon} [F] ${todo.content}${checkResult.filePath ? ` - ${checkResult.filePath}` : ""}`);
4797
+ }
4798
+ logger.info("\u68C0\u67E5\u540E\u7AEF\u4EFB\u52A1...");
4799
+ for (const todo of targetMilestone.backendTodos) {
4800
+ const checkResult = await checkBackendTask(todo);
4801
+ result.backendTasks.items.push(checkResult);
4802
+ if (checkResult.status === "pass") {
4803
+ result.backendTasks.completed++;
4804
+ }
4805
+ result.backendTasks.total++;
4806
+ const icon = checkResult.status === "pass" ? "\u2713" : checkResult.status === "fail" ? "\u2717" : "\u25CB";
4807
+ logger.info(` ${icon} [B] ${todo.content}${checkResult.filePath ? ` - ${checkResult.filePath}` : ""}`);
4808
+ }
4809
+ logger.info("\u9A8C\u8BC1 API \u5B9E\u73B0...");
4810
+ for (const todo of targetMilestone.backendTodos) {
4811
+ if (todo.apiAssociation) {
4812
+ const checkResult = await verifyApiImplementation(todo);
4813
+ result.apiVerification.items.push(checkResult);
4814
+ if (checkResult.status === "pass") {
4815
+ result.apiVerification.verified++;
4816
+ }
4817
+ result.apiVerification.total++;
4818
+ const icon = checkResult.status === "pass" ? "\u2713" : checkResult.status === "fail" ? "\u2717" : "\u25CB";
4819
+ logger.info(` ${icon} ${todo.apiAssociation}${checkResult.filePath ? ` - ${checkResult.filePath}` : ""}`);
4820
+ }
4821
+ }
4822
+ logger.info("\u68C0\u67E5\u8054\u8C03\u4EFB\u52A1...");
4823
+ for (const todo of targetMilestone.integrationTodos) {
4824
+ const checkResult = await checkIntegrationTask(todo);
4825
+ result.integrationTasks.items.push(checkResult);
4826
+ if (checkResult.status === "pass") {
4827
+ result.integrationTasks.completed++;
4828
+ }
4829
+ result.integrationTasks.total++;
4830
+ const icon = checkResult.status === "pass" ? "\u2713" : checkResult.status === "fail" ? "\u2717" : "\u25CB";
4831
+ logger.info(` ${icon} [I] ${todo.content}`);
4832
+ }
4833
+ return result;
4834
+ }
4835
+ async function checkFrontendTask(todo) {
4836
+ const frontendPaths = [
4837
+ `frontend/src/pages/${todo.content}.tsx`,
4838
+ `frontend/src/components/${todo.content}.tsx`,
4839
+ `frontend/src/components/${todo.content.replace(/\s+/g, "")}.tsx`,
4840
+ `frontend/src/views/${todo.content}.vue`,
4841
+ `frontend/src/components/${todo.content}.vue`
4842
+ ];
4843
+ for (const fp of frontendPaths) {
4844
+ if (await FileUtils.exists(fp)) {
4845
+ return {
4846
+ description: `[F] ${todo.content}`,
4847
+ status: "pass",
4848
+ details: "\u524D\u7AEF\u4EE3\u7801\u5B58\u5728",
4849
+ filePath: fp
4850
+ };
4851
+ }
4852
+ }
4853
+ const pathMatch = todo.content.match(/\s*-\s*(.+?\.(tsx|vue|js|jsx))\s*/);
4854
+ if (pathMatch) {
4855
+ const filePath = pathMatch[1];
4856
+ if (await FileUtils.exists(filePath)) {
4857
+ return {
4858
+ description: `[F] ${todo.content}`,
4859
+ status: "pass",
4860
+ details: "\u524D\u7AEF\u4EE3\u7801\u5B58\u5728",
4861
+ filePath
4862
+ };
4863
+ }
4864
+ }
4865
+ return {
4866
+ description: `[F] ${todo.content}`,
4867
+ status: "fail",
4868
+ details: "\u524D\u7AEF\u4EE3\u7801\u4E0D\u5B58\u5728\u6216\u8DEF\u5F84\u65E0\u6CD5\u9A8C\u8BC1"
4869
+ };
4870
+ }
4871
+ async function checkBackendTask(todo) {
4872
+ const backendPaths = [
4873
+ `backend/src/main/java/${todo.content.replace(/\s+/g, "/")}.java`,
4874
+ `backend/src/${todo.content.toLowerCase().replace(/\s+/g, "/")}.java`
4875
+ ];
4876
+ for (const fp of backendPaths) {
4877
+ if (await FileUtils.exists(fp)) {
4878
+ return {
4879
+ description: `[B] ${todo.content}`,
4880
+ status: "pass",
4881
+ details: "\u540E\u7AEF\u4EE3\u7801\u5B58\u5728",
4882
+ filePath: fp
4883
+ };
4884
+ }
4885
+ }
4886
+ const pathMatch = todo.content.match(/\s*-\s*(.+?\.(java|kt|py|go))\s*/);
4887
+ if (pathMatch) {
4888
+ const filePath = pathMatch[1];
4889
+ if (await FileUtils.exists(filePath)) {
4890
+ return {
4891
+ description: `[B] ${todo.content}`,
4892
+ status: "pass",
4893
+ details: "\u540E\u7AEF\u4EE3\u7801\u5B58\u5728",
4894
+ filePath
4895
+ };
4896
+ }
4897
+ }
4898
+ return {
4899
+ description: `[B] ${todo.content}`,
4900
+ status: "fail",
4901
+ details: "\u540E\u7AEF\u4EE3\u7801\u4E0D\u5B58\u5728\u6216\u8DEF\u5F84\u65E0\u6CD5\u9A8C\u8BC1"
4902
+ };
4903
+ }
4904
+ async function verifyApiImplementation(todo) {
4905
+ if (!todo.apiAssociation) {
4906
+ return {
4907
+ description: todo.apiAssociation || "API \u9A8C\u8BC1",
4908
+ status: "pending",
4909
+ details: "\u65E0 API \u5173\u8054\u4FE1\u606F"
4910
+ };
4911
+ }
4912
+ const apiMatch = todo.apiAssociation.match(/^(GET|POST|PUT|DELETE|PATCH)\s+\/(.+)$/);
4913
+ if (!apiMatch) {
4914
+ return {
4915
+ description: todo.apiAssociation,
4916
+ status: "pending",
4917
+ details: "\u65E0\u6CD5\u89E3\u6790 API \u683C\u5F0F"
4918
+ };
4919
+ }
4920
+ const method = apiMatch[1];
4921
+ const apiPath = apiMatch[2];
4922
+ const controllerPaths = [
4923
+ `backend/src/main/java/**/*Controller.java`,
4924
+ `backend/src/**/*Controller.java`
4925
+ ];
4926
+ const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
4927
+ for (const cf of controllerFiles) {
4928
+ const content = await FileUtils.read(cf);
4929
+ if (content.includes(apiPath) || content.includes(`"${apiPath}"`)) {
4930
+ const methodAnnotations = {
4931
+ GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
4932
+ POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
4933
+ PUT: ["@PutMapping", "@RequestMapping(method = RequestMethod.PUT)"],
4934
+ DELETE: ["@DeleteMapping", "@RequestMapping(method = RequestMethod.DELETE)"],
4935
+ PATCH: ["@PatchMapping", "@RequestMapping(method = RequestMethod.PATCH)"]
4936
+ };
4937
+ for (const annotation of methodAnnotations[method] || []) {
4938
+ if (content.includes(annotation)) {
4939
+ return {
4940
+ description: todo.apiAssociation,
4941
+ status: "pass",
4942
+ details: "API Controller \u5B9E\u73B0\u5B58\u5728",
4943
+ filePath: cf
4944
+ };
4945
+ }
4946
+ }
4947
+ }
4948
+ }
4949
+ return {
4950
+ description: todo.apiAssociation,
4951
+ status: "fail",
4952
+ details: "API Controller \u672A\u5B9E\u73B0"
4953
+ };
4954
+ }
4955
+ async function checkIntegrationTask(todo) {
4956
+ const integrationEvidence = [
4957
+ "**/*integration*.test.*",
4958
+ "**/*e2e*.test.*",
4959
+ "**/*IT.java",
4960
+ "docs/integration-test-results.md",
4961
+ "docs/session-logs/**"
4962
+ ];
4963
+ const sessionDir = "docs/sessions";
4964
+ if (await FileUtils.exists(sessionDir)) {
4965
+ const sessionFiles = await FileUtils.findFiles("*.md", sessionDir);
4966
+ const hasRecentSession = sessionFiles.some(
4967
+ (sf) => sf.includes("integration") || sf.includes("\u8054\u8C03")
4968
+ );
4969
+ if (hasRecentSession) {
4970
+ return {
4971
+ description: `[I] ${todo.content}`,
4972
+ status: "pass",
4973
+ details: "\u8054\u8C03\u8BC1\u636E\u5B58\u5728\uFF08session \u65E5\u5FD7\uFF09"
4974
+ };
4975
+ }
4976
+ }
4977
+ return {
4978
+ description: `[I] ${todo.content}`,
4979
+ status: "pending",
4980
+ details: "\u5EFA\u8BAE\u4EBA\u5DE5\u9A8C\u8BC1\u8054\u8C03\u7ED3\u679C"
4981
+ };
4982
+ }
4983
+ async function generateAcceptanceReport(result) {
4984
+ logger.newLine();
4985
+ logger.step("\u6B65\u9AA4 4/4: \u751F\u6210\u9A8C\u6536\u62A5\u544A...");
4986
+ logger.newLine();
4987
+ const reportDir = "docs/acceptance-reports";
4988
+ await FileUtils.ensureDir(reportDir);
4989
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4990
+ const specName = path13.basename(result.specFile, ".md");
4991
+ const milestoneSafe = result.milestone.replace(/[^a-zA-Z0-9]/g, "-");
4992
+ const reportFile = path13.join(reportDir, `${timestamp}_${specName}_${milestoneSafe}.md`);
4993
+ const report = generateMarkdownReport(result);
4994
+ await FileUtils.write(reportFile, report);
4995
+ logger.success(`\u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210: ${reportFile}`);
4996
+ logger.newLine();
4997
+ console.log(generateConsoleReport(result));
4998
+ }
4999
+ function generateMarkdownReport(result) {
5000
+ const lines = [];
5001
+ lines.push("# \u9A8C\u6536\u62A5\u544A");
5002
+ lines.push("");
5003
+ lines.push(`**Spec \u6587\u4EF6**: ${result.specFile}`);
5004
+ lines.push(`**Milestone**: ${result.milestone}`);
5005
+ lines.push(`**\u9A8C\u6536\u65F6\u95F4**: ${result.checkTime}`);
5006
+ lines.push("");
5007
+ lines.push("## \u68C0\u67E5\u7ED3\u679C\u6C47\u603B");
5008
+ lines.push("");
5009
+ lines.push("| \u68C0\u67E5\u9879 | \u72B6\u6001 |");
5010
+ lines.push("|--------|------|");
5011
+ lines.push(
5012
+ `| \u524D\u7AEF\u4EFB\u52A1 | ${result.frontendTasks.completed}/${result.frontendTasks.total} \u5B8C\u6210 |`
5013
+ );
5014
+ lines.push(
5015
+ `| \u540E\u7AEF\u4EFB\u52A1 | ${result.backendTasks.completed}/${result.backendTasks.total} \u5B8C\u6210 |`
5016
+ );
5017
+ lines.push(`| API \u9A8C\u8BC1 | ${result.apiVerification.verified}/${result.apiVerification.total} \u5B9E\u73B0 |`);
5018
+ lines.push(
5019
+ `| \u8054\u8C03\u4EFB\u52A1 | ${result.integrationTasks.completed}/${result.integrationTasks.total} \u5B8C\u6210 |`
5020
+ );
5021
+ lines.push("");
5022
+ lines.push("## \u8BE6\u7EC6\u8BB0\u5F55");
5023
+ lines.push("");
5024
+ lines.push("### \u524D\u7AEF\u4EFB\u52A1");
5025
+ lines.push("");
5026
+ for (const item of result.frontendTasks.items) {
5027
+ const icon = item.status === "pass" ? "\u2713" : item.status === "fail" ? "\u2717" : "\u25CB";
5028
+ lines.push(`- ${icon} ${item.description} - ${item.details || ""} ${item.filePath ? `(\`${item.filePath}\`)` : ""}`);
5029
+ }
5030
+ lines.push("");
5031
+ lines.push("### \u540E\u7AEF\u4EFB\u52A1");
5032
+ lines.push("");
5033
+ for (const item of result.backendTasks.items) {
5034
+ const icon = item.status === "pass" ? "\u2713" : item.status === "fail" ? "\u2717" : "\u25CB";
5035
+ lines.push(`- ${icon} ${item.description} - ${item.details || ""} ${item.filePath ? `(\`${item.filePath}\`)` : ""}`);
5036
+ }
5037
+ lines.push("");
5038
+ lines.push("### API \u9A8C\u8BC1");
5039
+ lines.push("");
5040
+ for (const item of result.apiVerification.items) {
5041
+ const icon = item.status === "pass" ? "\u2713" : item.status === "fail" ? "\u2717" : "\u25CB";
5042
+ lines.push(`- ${icon} ${item.description} - ${item.details || ""} ${item.filePath ? `(\`${item.filePath}\`)` : ""}`);
5043
+ }
5044
+ lines.push("");
5045
+ lines.push("### \u8054\u8C03\u4EFB\u52A1");
5046
+ lines.push("");
5047
+ for (const item of result.integrationTasks.items) {
5048
+ const icon = item.status === "pass" ? "\u2713" : item.status === "fail" ? "\u2717" : "\u26A0";
5049
+ lines.push(`- ${icon} ${item.description} - ${item.details || ""}`);
5050
+ }
5051
+ lines.push("");
5052
+ if (result.issues.length > 0) {
5053
+ lines.push("## \u53D1\u73B0\u7684\u95EE\u9898");
5054
+ lines.push("");
5055
+ lines.push("| \u95EE\u9898 | \u7C7B\u578B | \u4E25\u91CD\u7A0B\u5EA6 | \u72B6\u6001 |");
5056
+ lines.push("|------|------|---------|------|");
5057
+ for (const issue of result.issues) {
5058
+ lines.push(`| ${issue.title} | ${issue.type} | ${issue.severity} | ${issue.status} |`);
5059
+ }
5060
+ lines.push("");
5061
+ lines.push("**\u64CD\u4F5C**:");
5062
+ lines.push(`- \u8FD0\u884C \`team-cli bugfix ${result.specFile}\` \u67E5\u770B\u8BE6\u60C5`);
5063
+ lines.push("- \u6216\u8FD0\u884C `team-cli hotfix` \u7ACB\u5373\u4FEE\u590D");
5064
+ lines.push("");
5065
+ }
5066
+ lines.push("## \u4E0B\u4E00\u6B65\u5EFA\u8BAE");
5067
+ lines.push("");
5068
+ if (result.issues.length > 0) {
5069
+ lines.push("- [ ] \u4FEE\u590D\u53D1\u73B0\u7684\u95EE\u9898");
5070
+ }
5071
+ lines.push("- [ ] \u8FD0\u884C `team-cli dev` \u7EE7\u7EED\u5F00\u53D1");
5072
+ lines.push("- [ ] \u8FD0\u884C `team-cli accept` \u91CD\u65B0\u9A8C\u6536");
5073
+ lines.push("");
5074
+ return lines.join("\n");
5075
+ }
5076
+ function generateConsoleReport(result) {
5077
+ const lines = [];
5078
+ lines.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
5079
+ lines.push("\u2502 \u9A8C \u6536 \u7ED3 \u679C \u6458 \u8981 \u2502");
5080
+ lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
5081
+ lines.push(`\u2502 Spec: ${result.specFile.padEnd(40)}\u2502`);
5082
+ lines.push(`\u2502 Milestone: ${result.milestone.padEnd(37)}\u2502`);
5083
+ lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
5084
+ lines.push(
5085
+ `\u2502 \u524D\u7AEF\u4EFB\u52A1: ${result.frontendTasks.completed}/${result.frontendTasks.total} \u5B8C\u6210`.padEnd(47) + "\u2502"
5086
+ );
5087
+ lines.push(
5088
+ `\u2502 \u540E\u7AEF\u4EFB\u52A1: ${result.backendTasks.completed}/${result.backendTasks.total} \u5B8C\u6210`.padEnd(47) + "\u2502"
5089
+ );
5090
+ lines.push(
5091
+ `\u2502 API \u9A8C\u8BC1: ${result.apiVerification.verified}/${result.apiVerification.total} \u5B9E\u73B0`.padEnd(47) + "\u2502"
5092
+ );
5093
+ lines.push(
5094
+ `\u2502 \u8054\u8C03\u4EFB\u52A1: ${result.integrationTasks.completed}/${result.integrationTasks.total} \u5B8C\u6210`.padEnd(47) + "\u2502"
5095
+ );
5096
+ lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
5097
+ const totalIssues = result.issues.length;
5098
+ if (totalIssues > 0) {
5099
+ lines.push(`\u2502 \u26A0 \u53D1\u73B0\u95EE\u9898: ${totalIssues} \u4E2A`.padEnd(47) + "\u2502");
5100
+ } else {
5101
+ lines.push(`\u2502 \u2713 \u9A8C\u6536\u901A\u8FC7`.padEnd(47) + "\u2502");
5102
+ }
5103
+ lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
5104
+ return lines.join("\n");
5105
+ }
5106
+ async function handleIssues(result) {
5107
+ logger.newLine();
5108
+ logger.warn(`\u53D1\u73B0 ${result.issues.length} \u4E2A\u95EE\u9898\u9700\u8981\u5904\u7406`);
5109
+ const bugfixDir = "docs/bugfixes";
5110
+ await FileUtils.ensureDir(bugfixDir);
5111
+ for (const issue of result.issues) {
5112
+ const bugfixFile = path13.join(
5113
+ bugfixDir,
5114
+ `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}_${issue.id}.md`
5115
+ );
5116
+ const bugfixContent = generateBugfixContent(result, issue);
5117
+ await FileUtils.write(bugfixFile, bugfixContent);
5118
+ logger.info(` \u5DF2\u521B\u5EFA Bugfix \u8BB0\u5F55: ${bugfixFile}`);
5119
+ }
5120
+ logger.newLine();
5121
+ const { fixNow } = await inquirer6.prompt([
5122
+ {
5123
+ type: "confirm",
5124
+ name: "fixNow",
5125
+ message: "\u662F\u5426\u7ACB\u5373\u4FEE\u590D\u53D1\u73B0\u7684\u95EE\u9898?",
5126
+ default: false
5127
+ }
5128
+ ]);
5129
+ if (fixNow) {
5130
+ logger.info("\u8BF7\u8FD0\u884C `team-cli hotfix` \u7ACB\u5373\u4FEE\u590D\u95EE\u9898");
5131
+ } else {
5132
+ logger.info(`\u95EE\u9898\u8BB0\u5F55\u5DF2\u4FDD\u5B58\u5230 ${bugfixDir} \u76EE\u5F55`);
5133
+ logger.info("\u8FD0\u884C `team-cli bugfix` \u67E5\u770B\u6240\u6709\u95EE\u9898\u8BB0\u5F55");
5134
+ }
5135
+ }
5136
+ function generateBugfixContent(result, issue) {
5137
+ const lines = [];
5138
+ lines.push("# Bugfix \u8BB0\u5F55");
5139
+ lines.push("");
5140
+ lines.push(`**ID**: ${issue.id}`);
5141
+ lines.push(`**\u521B\u5EFA\u65F6\u95F4**: ${issue.createdAt}`);
5142
+ lines.push(`**\u72B6\u6001**: ${issue.status}`);
5143
+ lines.push("");
5144
+ lines.push("## \u95EE\u9898\u63CF\u8FF0");
5145
+ lines.push("");
5146
+ lines.push(issue.description);
5147
+ lines.push("");
5148
+ lines.push("## \u6240\u5C5E\u8303\u56F4");
5149
+ lines.push("");
5150
+ lines.push(`- Spec: ${result.specFile}`);
5151
+ lines.push(`- Milestone: ${result.milestone}`);
5152
+ if (issue.todo) {
5153
+ lines.push(`- Todo: ${issue.todo}`);
5154
+ }
5155
+ lines.push("");
5156
+ lines.push("## \u4E25\u91CD\u7A0B\u5EA6");
5157
+ lines.push("");
5158
+ lines.push(`- ${issue.severity.toUpperCase()}`);
5159
+ lines.push("");
5160
+ lines.push("## \u95EE\u9898\u7C7B\u578B");
5161
+ lines.push("");
5162
+ lines.push(`- ${issue.type}`);
5163
+ lines.push("");
5164
+ lines.push("## \u89E3\u51B3\u65B9\u6848");
5165
+ lines.push("");
5166
+ lines.push("<!-- TODO: \u586B\u5199\u89E3\u51B3\u65B9\u6848 -->");
5167
+ lines.push("");
5168
+ lines.push("## \u9A8C\u8BC1\u6B65\u9AA4");
5169
+ lines.push("");
5170
+ lines.push("1. ");
5171
+ lines.push("2. ");
5172
+ lines.push("3. ");
5173
+ lines.push("");
5174
+ return lines.join("\n");
5175
+ }
5176
+ async function syncSpecStatus(specFile, result) {
5177
+ const content = await FileUtils.read(specFile);
5178
+ const lines = content.split("\n");
5179
+ let inTargetMilestone = false;
5180
+ let milestoneIndex = -1;
5181
+ for (let i = 0; i < lines.length; i++) {
5182
+ const line = lines[i];
5183
+ if (line.includes(result.milestone)) {
5184
+ inTargetMilestone = true;
5185
+ milestoneIndex = i;
5186
+ continue;
5187
+ }
5188
+ if (inTargetMilestone && line.match(/^###\s+Milestone/)) {
5189
+ break;
5190
+ }
5191
+ }
5192
+ if (milestoneIndex !== -1) {
5193
+ let completedTasks = 0;
5194
+ let totalTasks = 0;
5195
+ for (let i = milestoneIndex + 1; i < lines.length; i++) {
5196
+ const line = lines[i];
5197
+ if (line.match(/^###\s+Milestone/)) {
5198
+ break;
5199
+ }
5200
+ if (line.match(/^-\s+\[/)) {
5201
+ totalTasks++;
5202
+ if (line.match(/^-\s+\[x\]/)) {
5203
+ completedTasks++;
5204
+ }
5205
+ }
5206
+ }
5207
+ const progressLine = `${result.milestone} [${completedTasks}/${totalTasks}]`;
5208
+ const linesBefore = lines.slice(0, milestoneIndex);
5209
+ const linesAfter = lines.slice(milestoneIndex + 1);
5210
+ if (!lines[milestoneIndex].includes(`[${completedTasks}/${totalTasks}]`)) {
5211
+ const updatedMilestoneLine = lines[milestoneIndex].replace(
5212
+ /(\s*\[\d+\/\d+\])?$/,
5213
+ ` [${completedTasks}/${totalTasks}]`
5214
+ );
5215
+ lines[milestoneIndex] = updatedMilestoneLine;
5216
+ await FileUtils.write(specFile, lines.join("\n"));
5217
+ logger.info(`\u5DF2\u540C\u6B65\u91CC\u7A0B\u7891\u8FDB\u5EA6: [${completedTasks}/${totalTasks}]`);
5218
+ }
5219
+ }
5220
+ }
5221
+
5222
+ // src/commands/lint.ts
5223
+ init_esm_shims();
5224
+ init_utils();
5225
+ init_logger();
5226
+ import { Command as Command8 } from "commander";
4427
5227
  import { execa as execa3 } from "execa";
4428
- var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").option("--no-type-check", "\u8DF3\u8FC7 TypeScript \u7C7B\u578B\u68C0\u67E5").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
5228
+ var lintCommand = new Command8("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").option("--no-type-check", "\u8DF3\u8FC7 TypeScript \u7C7B\u578B\u68C0\u67E5").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
4429
5229
  try {
4430
5230
  logger.header("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5");
4431
5231
  logger.newLine();
@@ -4560,9 +5360,9 @@ var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D
4560
5360
  init_esm_shims();
4561
5361
  init_utils();
4562
5362
  init_logger();
4563
- import { Command as Command8 } from "commander";
4564
- import path13 from "path";
4565
- var statusCommand = new Command8("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
5363
+ import { Command as Command9 } from "commander";
5364
+ import path14 from "path";
5365
+ var statusCommand = new Command9("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
4566
5366
  try {
4567
5367
  logger.header("\u9879\u76EE\u72B6\u6001");
4568
5368
  logger.newLine();
@@ -4619,7 +5419,7 @@ async function displayFeatureInventory() {
4619
5419
  }
4620
5420
  const inventory = [];
4621
5421
  for (const file of specs) {
4622
- const filePath = path13.join(specDir, file);
5422
+ const filePath = path14.join(specDir, file);
4623
5423
  const content = await FileUtils.read(filePath);
4624
5424
  const status = SpecUtils.parseSpecStatus(content);
4625
5425
  const statusWithIcon = SpecUtils.getStatusWithIcon(status);
@@ -4679,7 +5479,7 @@ async function displayRecentActivity() {
4679
5479
  }
4680
5480
  const sorted = files.sort().reverse().slice(0, 5);
4681
5481
  for (const file of sorted) {
4682
- const filePath = path13.join(sessionDir, file);
5482
+ const filePath = path14.join(sessionDir, file);
4683
5483
  const stat = await FileUtils.read(filePath);
4684
5484
  const specMatch = stat.match(/\*\*Spec\*\*:\s*(.+)/);
4685
5485
  const spec = specMatch ? specMatch[1].trim() : "\u672A\u77E5";
@@ -4698,10 +5498,10 @@ async function displayRecentActivity() {
4698
5498
  init_esm_shims();
4699
5499
  init_utils();
4700
5500
  init_logger();
4701
- import { Command as Command9 } from "commander";
4702
- import path14 from "path";
4703
- import inquirer6 from "inquirer";
4704
- var detectDepsCommand = new Command9("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
5501
+ import { Command as Command10 } from "commander";
5502
+ import path15 from "path";
5503
+ import inquirer7 from "inquirer";
5504
+ var detectDepsCommand = new Command10("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
4705
5505
  try {
4706
5506
  logger.header("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
4707
5507
  logger.newLine();
@@ -4727,7 +5527,7 @@ var detectDepsCommand = new Command9("detect-deps").argument("[spec-file]", "Spe
4727
5527
  process.exit(1);
4728
5528
  }
4729
5529
  for (const spec of specs) {
4730
- const specPath = path14.join(specsDir, spec);
5530
+ const specPath = path15.join(specsDir, spec);
4731
5531
  logger.step(`\u5904\u7406: ${spec}`);
4732
5532
  await detectDependencies(specPath);
4733
5533
  logger.newLine();
@@ -4753,7 +5553,7 @@ async function detectDependencies(specFile) {
4753
5553
  logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
4754
5554
  const projectDir = ".";
4755
5555
  const allDeps = /* @__PURE__ */ new Set();
4756
- const backendDir = path14.join(projectDir, "backend");
5556
+ const backendDir = path15.join(projectDir, "backend");
4757
5557
  const backendExists = await FileUtils.exists(backendDir);
4758
5558
  if (backendExists) {
4759
5559
  const apiDeps = await scanBackendApiCalls(backendDir);
@@ -4763,7 +5563,7 @@ async function detectDependencies(specFile) {
4763
5563
  const serviceDeps = await scanBackendServiceRefs(backendDir);
4764
5564
  serviceDeps.forEach((d) => allDeps.add(d));
4765
5565
  }
4766
- const frontendDir = path14.join(projectDir, "frontend");
5566
+ const frontendDir = path15.join(projectDir, "frontend");
4767
5567
  const frontendExists = await FileUtils.exists(frontendDir);
4768
5568
  if (frontendExists) {
4769
5569
  const frontendDeps = await scanFrontendApiCalls(frontendDir);
@@ -4789,7 +5589,7 @@ async function detectDependencies(specFile) {
4789
5589
  logger.step(`- ${spec}`);
4790
5590
  }
4791
5591
  logger.newLine();
4792
- const answers = await inquirer6.prompt([
5592
+ const answers = await inquirer7.prompt([
4793
5593
  {
4794
5594
  type: "confirm",
4795
5595
  name: "autoUpdate",
@@ -4804,11 +5604,11 @@ async function detectDependencies(specFile) {
4804
5604
  }
4805
5605
  async function scanBackendApiCalls(backendDir) {
4806
5606
  const deps = [];
4807
- const srcDir = path14.join(backendDir, "src");
5607
+ const srcDir = path15.join(backendDir, "src");
4808
5608
  try {
4809
5609
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
4810
5610
  for (const file of javaFiles) {
4811
- const filePath = path14.join(srcDir, file);
5611
+ const filePath = path15.join(srcDir, file);
4812
5612
  const content = await FileUtils.read(filePath);
4813
5613
  const pathRegex = /"(\/api\/[^"]+)"/g;
4814
5614
  let match;
@@ -4826,11 +5626,11 @@ async function scanBackendApiCalls(backendDir) {
4826
5626
  }
4827
5627
  async function scanBackendEntityRelations(backendDir) {
4828
5628
  const deps = [];
4829
- const srcDir = path14.join(backendDir, "src");
5629
+ const srcDir = path15.join(backendDir, "src");
4830
5630
  try {
4831
5631
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
4832
5632
  for (const file of javaFiles) {
4833
- const filePath = path14.join(srcDir, file);
5633
+ const filePath = path15.join(srcDir, file);
4834
5634
  const content = await FileUtils.read(filePath);
4835
5635
  if (content.includes("@JoinColumn") || content.includes("@ManyToOne") || content.includes("@OneToMany")) {
4836
5636
  const typeRegex = /type\s*=\s*(\w+)/g;
@@ -4846,11 +5646,11 @@ async function scanBackendEntityRelations(backendDir) {
4846
5646
  }
4847
5647
  async function scanBackendServiceRefs(backendDir) {
4848
5648
  const deps = [];
4849
- const srcDir = path14.join(backendDir, "src");
5649
+ const srcDir = path15.join(backendDir, "src");
4850
5650
  try {
4851
5651
  const javaFiles = await FileUtils.findFiles("*.java", srcDir);
4852
5652
  for (const file of javaFiles) {
4853
- const filePath = path14.join(srcDir, file);
5653
+ const filePath = path15.join(srcDir, file);
4854
5654
  const content = await FileUtils.read(filePath);
4855
5655
  const serviceRegex = /private\s+(\w+)Service/g;
4856
5656
  let match;
@@ -4866,11 +5666,11 @@ async function scanBackendServiceRefs(backendDir) {
4866
5666
  }
4867
5667
  async function scanFrontendApiCalls(frontendDir) {
4868
5668
  const deps = [];
4869
- const srcDir = path14.join(frontendDir, "src");
5669
+ const srcDir = path15.join(frontendDir, "src");
4870
5670
  try {
4871
5671
  const tsFiles = await FileUtils.findFiles("*.{ts,tsx,js,jsx}", srcDir);
4872
5672
  for (const file of tsFiles) {
4873
- const filePath = path14.join(srcDir, file);
5673
+ const filePath = path15.join(srcDir, file);
4874
5674
  const content = await FileUtils.read(filePath);
4875
5675
  const pathRegex = /"(\/api\/[^"]+)"/g;
4876
5676
  let match;
@@ -4959,9 +5759,9 @@ ${deps.map((d) => ` - [x] ${d}`).join("\n")}
4959
5759
  init_esm_shims();
4960
5760
  init_utils();
4961
5761
  init_logger();
4962
- import { Command as Command10 } from "commander";
4963
- import path15 from "path";
4964
- var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
5762
+ import { Command as Command11 } from "commander";
5763
+ import path16 from "path";
5764
+ var syncMemoryCommand = new Command11("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
4965
5765
  try {
4966
5766
  logger.header("\u540C\u6B65 AI_MEMORY.md");
4967
5767
  logger.newLine();
@@ -4993,7 +5793,7 @@ var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 A
4993
5793
  });
4994
5794
  async function syncFeatureInventory(aiMemoryFile, projectDir) {
4995
5795
  logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
4996
- const specsDir = path15.join(projectDir, "docs/specs");
5796
+ const specsDir = path16.join(projectDir, "docs/specs");
4997
5797
  const exists = await FileUtils.exists(specsDir);
4998
5798
  if (!exists) {
4999
5799
  return;
@@ -5008,7 +5808,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
5008
5808
  for (const specFile of specs) {
5009
5809
  const name = specFile.replace(".md", "");
5010
5810
  const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5011
- const specPath = path15.join(specsDir, specFile);
5811
+ const specPath = path16.join(specsDir, specFile);
5012
5812
  const content = await FileUtils.read(specPath);
5013
5813
  const status = SpecUtils.parseSpecStatus(content);
5014
5814
  const statusWithIcon = SpecUtils.getStatusWithIcon(status);
@@ -5027,7 +5827,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
5027
5827
  }
5028
5828
  async function syncApiInventory(aiMemoryFile, projectDir) {
5029
5829
  logger.step("\u540C\u6B65 API \u5217\u8868...");
5030
- const backendDir = path15.join(projectDir, "backend");
5830
+ const backendDir = path16.join(projectDir, "backend");
5031
5831
  const exists = await FileUtils.exists(backendDir);
5032
5832
  if (!exists) {
5033
5833
  return;
@@ -5037,13 +5837,13 @@ async function syncApiInventory(aiMemoryFile, projectDir) {
5037
5837
  lines.push("");
5038
5838
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210");
5039
5839
  lines.push("");
5040
- const srcDir = path15.join(backendDir, "src");
5840
+ const srcDir = path16.join(backendDir, "src");
5041
5841
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
5042
5842
  if (controllers.length === 0) {
5043
5843
  lines.push("\u6682\u65E0 API");
5044
5844
  } else {
5045
5845
  for (const controllerFile of controllers) {
5046
- const controllerPath = path15.join(srcDir, controllerFile);
5846
+ const controllerPath = path16.join(srcDir, controllerFile);
5047
5847
  const controllerName = controllerFile.replace(".java", "");
5048
5848
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
5049
5849
  lines.push(`### ${module} \u6A21\u5757`);
@@ -5120,7 +5920,7 @@ function extractMethodComment(content, methodName) {
5120
5920
  }
5121
5921
  async function syncDataModels(aiMemoryFile, projectDir) {
5122
5922
  logger.step("\u540C\u6B65\u6570\u636E\u6A21\u578B...");
5123
- const backendDir = path15.join(projectDir, "backend");
5923
+ const backendDir = path16.join(projectDir, "backend");
5124
5924
  const exists = await FileUtils.exists(backendDir);
5125
5925
  if (!exists) {
5126
5926
  return;
@@ -5130,7 +5930,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
5130
5930
  lines.push("");
5131
5931
  lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Entity \u751F\u6210");
5132
5932
  lines.push("");
5133
- const srcDir = path15.join(backendDir, "src");
5933
+ const srcDir = path16.join(backendDir, "src");
5134
5934
  const entities = await FileUtils.findFiles("*Entity.java", srcDir);
5135
5935
  if (entities.length === 0) {
5136
5936
  lines.push("\u6682\u65E0\u6570\u636E\u6A21\u578B");
@@ -5138,7 +5938,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
5138
5938
  lines.push("| \u6A21\u578B | \u8BF4\u660E | \u5B57\u6BB5 | \u5173\u8054 |");
5139
5939
  lines.push("|------|------|------|------|");
5140
5940
  for (const entityFile of entities) {
5141
- const entityPath = path15.join(srcDir, entityFile);
5941
+ const entityPath = path16.join(srcDir, entityFile);
5142
5942
  const entityName = entityFile.replace(".java", "").replace(/Entity$/, "");
5143
5943
  const displayName = entityName.split(/(?=[A-Z])/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
5144
5944
  const content = await FileUtils.read(entityPath);
@@ -5240,26 +6040,26 @@ async function syncTemplateVersions(aiMemoryFile, projectDir) {
5240
6040
  await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
5241
6041
  }
5242
6042
  function extractRepoName(repository) {
5243
- let path20 = repository;
5244
- path20 = path20.replace(/^https?:\/\//, "");
5245
- path20 = path20.replace(/^git@/, "");
5246
- const parts = path20.split("/");
6043
+ let path21 = repository;
6044
+ path21 = path21.replace(/^https?:\/\//, "");
6045
+ path21 = path21.replace(/^git@/, "");
6046
+ const parts = path21.split("/");
5247
6047
  if (parts.length > 1) {
5248
- path20 = parts.slice(1).join("/");
6048
+ path21 = parts.slice(1).join("/");
5249
6049
  }
5250
- path20 = path20.replace(/\.git$/, "");
5251
- return path20;
6050
+ path21 = path21.replace(/\.git$/, "");
6051
+ return path21;
5252
6052
  }
5253
6053
 
5254
6054
  // src/commands/check-api.ts
5255
6055
  init_esm_shims();
5256
6056
  init_utils();
5257
6057
  init_logger();
5258
- import { Command as Command11 } from "commander";
5259
- import path16 from "path";
5260
- import inquirer7 from "inquirer";
6058
+ import { Command as Command12 } from "commander";
6059
+ import path17 from "path";
6060
+ import inquirer8 from "inquirer";
5261
6061
  import { Listr as Listr6 } from "listr2";
5262
- var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
6062
+ var checkApiCommand = new Command12("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
5263
6063
  try {
5264
6064
  logger.header("API \u68C0\u67E5");
5265
6065
  logger.newLine();
@@ -5269,7 +6069,7 @@ var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\u
5269
6069
  logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
5270
6070
  process.exit(1);
5271
6071
  }
5272
- const answers = await inquirer7.prompt([
6072
+ const answers = await inquirer8.prompt([
5273
6073
  {
5274
6074
  type: "list",
5275
6075
  name: "checkType",
@@ -5320,7 +6120,7 @@ var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\u
5320
6120
  }
5321
6121
  });
5322
6122
  async function checkApiConflicts(projectDir) {
5323
- const backendDir = path16.join(projectDir, "backend");
6123
+ const backendDir = path17.join(projectDir, "backend");
5324
6124
  const exists = await FileUtils.exists(backendDir);
5325
6125
  if (!exists) {
5326
6126
  logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE");
@@ -5329,10 +6129,10 @@ async function checkApiConflicts(projectDir) {
5329
6129
  logger.step("\u626B\u63CF\u540E\u7AEF API...");
5330
6130
  logger.newLine();
5331
6131
  const apiMap = /* @__PURE__ */ new Map();
5332
- const srcDir = path16.join(backendDir, "src");
6132
+ const srcDir = path17.join(backendDir, "src");
5333
6133
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
5334
6134
  for (const controllerFile of controllers) {
5335
- const controllerPath = path16.join(srcDir, controllerFile);
6135
+ const controllerPath = path17.join(srcDir, controllerFile);
5336
6136
  const apis = await extractApisFromController(controllerPath);
5337
6137
  for (const api of apis) {
5338
6138
  const key = `${api.method}:${api.path}`;
@@ -5363,8 +6163,8 @@ async function checkApiConflicts(projectDir) {
5363
6163
  }
5364
6164
  }
5365
6165
  async function detectApiChanges(projectDir) {
5366
- const backendDir = path16.join(projectDir, "backend");
5367
- const registryFile = path16.join(projectDir, "docs/api-registry.md");
6166
+ const backendDir = path17.join(projectDir, "backend");
6167
+ const registryFile = path17.join(projectDir, "docs/api-registry.md");
5368
6168
  const registryExists = await FileUtils.exists(registryFile);
5369
6169
  if (!registryExists) {
5370
6170
  logger.info("API Registry \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u53D8\u66F4\u68C0\u6D4B");
@@ -5376,10 +6176,10 @@ async function detectApiChanges(projectDir) {
5376
6176
  const registryContent = await FileUtils.read(registryFile);
5377
6177
  const existingApis = extractApisFromRegistry(registryContent);
5378
6178
  const currentApis = /* @__PURE__ */ new Map();
5379
- const srcDir = path16.join(backendDir, "src");
6179
+ const srcDir = path17.join(backendDir, "src");
5380
6180
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
5381
6181
  for (const controllerFile of controllers) {
5382
- const controllerPath = path16.join(srcDir, controllerFile);
6182
+ const controllerPath = path17.join(srcDir, controllerFile);
5383
6183
  const apis = await extractApisFromController(controllerPath);
5384
6184
  for (const api of apis) {
5385
6185
  const key = `${api.method}:${api.path}`;
@@ -5441,9 +6241,9 @@ async function detectApiChanges(projectDir) {
5441
6241
  }
5442
6242
  }
5443
6243
  async function generateApiRegistry(projectDir) {
5444
- const registryFile = path16.join(projectDir, "docs/api-registry.md");
6244
+ const registryFile = path17.join(projectDir, "docs/api-registry.md");
5445
6245
  logger.step("\u626B\u63CF\u5E76\u751F\u6210 API Registry...");
5446
- await FileUtils.ensureDir(path16.dirname(registryFile));
6246
+ await FileUtils.ensureDir(path17.dirname(registryFile));
5447
6247
  const header = `# API Registry
5448
6248
 
5449
6249
  > \u672C\u6587\u4EF6\u8BB0\u5F55\u6240\u6709 API \u7684\u5B9A\u4E49\u3001\u7248\u672C\u548C\u53D8\u66F4\u5386\u53F2
@@ -5472,14 +6272,14 @@ async function generateApiRegistry(projectDir) {
5472
6272
  *\u6700\u540E\u66F4\u65B0: ${DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss")}*
5473
6273
  `;
5474
6274
  let content = header;
5475
- const backendDir = path16.join(projectDir, "backend");
6275
+ const backendDir = path17.join(projectDir, "backend");
5476
6276
  const exists = await FileUtils.exists(backendDir);
5477
6277
  if (exists) {
5478
- const srcDir = path16.join(backendDir, "src");
6278
+ const srcDir = path17.join(backendDir, "src");
5479
6279
  const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
5480
6280
  const moduleMap = /* @__PURE__ */ new Map();
5481
6281
  for (const controllerFile of controllers) {
5482
- const controllerPath = path16.join(srcDir, controllerFile);
6282
+ const controllerPath = path17.join(srcDir, controllerFile);
5483
6283
  const controllerName = controllerFile.replace(".java", "");
5484
6284
  const module = controllerName.replace(/Controller$/, "").toLowerCase();
5485
6285
  if (!moduleMap.has(module)) {
@@ -5563,10 +6363,10 @@ function extractApisFromRegistry(registryContent) {
5563
6363
  let match;
5564
6364
  while ((match = apiRegex.exec(registryContent)) !== null) {
5565
6365
  const method = match[1];
5566
- const path20 = match[2].trim();
6366
+ const path21 = match[2].trim();
5567
6367
  const description = match[3].trim();
5568
- const key = `${method}:${path20}`;
5569
- apis.set(key, { method, path: path20, description });
6368
+ const key = `${method}:${path21}`;
6369
+ apis.set(key, { method, path: path21, description });
5570
6370
  }
5571
6371
  return apis;
5572
6372
  }
@@ -5587,10 +6387,10 @@ function extractMethodComment2(content, methodName) {
5587
6387
  init_esm_shims();
5588
6388
  init_utils();
5589
6389
  init_logger();
5590
- import { Command as Command12 } from "commander";
5591
- import path17 from "path";
5592
- import inquirer8 from "inquirer";
5593
- var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
6390
+ import { Command as Command13 } from "commander";
6391
+ import path18 from "path";
6392
+ import inquirer9 from "inquirer";
6393
+ var logsCommand = new Command13("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
5594
6394
  try {
5595
6395
  logger.header("\u4F1A\u8BDD\u65E5\u5FD7");
5596
6396
  logger.newLine();
@@ -5613,7 +6413,7 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
5613
6413
  case "":
5614
6414
  case "today": {
5615
6415
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5616
- targetDir = path17.join(sessionsDir, today);
6416
+ targetDir = path18.join(sessionsDir, today);
5617
6417
  const todayExists = await FileUtils.exists(targetDir);
5618
6418
  if (!todayExists) {
5619
6419
  logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
@@ -5629,7 +6429,7 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
5629
6429
  break;
5630
6430
  }
5631
6431
  default: {
5632
- targetDir = path17.join(sessionsDir, filter);
6432
+ targetDir = path18.join(sessionsDir, filter);
5633
6433
  const dateExists = await FileUtils.exists(targetDir);
5634
6434
  if (!dateExists) {
5635
6435
  logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
@@ -5653,11 +6453,11 @@ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668
5653
6453
  process.exit(0);
5654
6454
  }
5655
6455
  for (let i = 0; i < logs.length; i++) {
5656
- const relPath = path17.relative(sessionsDir, logs[i]);
6456
+ const relPath = path18.relative(sessionsDir, logs[i]);
5657
6457
  logger.step(`${i + 1}) ${relPath}`);
5658
6458
  }
5659
6459
  logger.newLine();
5660
- const answers = await inquirer8.prompt([
6460
+ const answers = await inquirer9.prompt([
5661
6461
  {
5662
6462
  type: "input",
5663
6463
  name: "selection",
@@ -5694,7 +6494,7 @@ async function collectLogFiles(targetDir) {
5694
6494
  const allFiles = await FileUtils.findFiles("*.md", targetDir);
5695
6495
  const filtered = allFiles.filter((f) => f !== "index.md");
5696
6496
  for (const file of filtered) {
5697
- const filePath = path17.join(targetDir, file);
6497
+ const filePath = path18.join(targetDir, file);
5698
6498
  const stat = await FileUtils.exists(filePath);
5699
6499
  if (stat) {
5700
6500
  logs.push(filePath);
@@ -5707,14 +6507,14 @@ async function collectLogFiles(targetDir) {
5707
6507
 
5708
6508
  // src/commands/update.ts
5709
6509
  init_esm_shims();
5710
- import { Command as Command13 } from "commander";
5711
- import path18 from "path";
6510
+ import { Command as Command14 } from "commander";
6511
+ import path19 from "path";
5712
6512
  init_logger();
5713
6513
  init_utils();
5714
6514
  import { execa as execa4 } from "execa";
5715
- import inquirer9 from "inquirer";
6515
+ import inquirer10 from "inquirer";
5716
6516
  import fs4 from "fs-extra";
5717
- var updateCommand = new Command13("update").description("\u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C").option("-f, --frontend", "\u68C0\u67E5\u524D\u7AEF\u6A21\u677F\u66F4\u65B0").option("-b, --backend", "\u68C0\u67E5\u540E\u7AEF\u6A21\u677F\u66F4\u65B0").option("-a, --all", "\u68C0\u67E5\u6240\u6709\u6A21\u677F (\u9ED8\u8BA4)").option("-t, --tag <tag>", "\u66F4\u65B0\u5230\u6307\u5B9A\u6807\u7B7E").option("-B, --branch <branch>", "\u66F4\u65B0\u5230\u6307\u5B9A\u5206\u652F").option("--dry-run", "\u9884\u89C8\u66F4\u65B0\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
6517
+ var updateCommand = new Command14("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) => {
5718
6518
  try {
5719
6519
  logger.header("\u6A21\u677F\u7248\u672C\u68C0\u67E5");
5720
6520
  logger.newLine();
@@ -5786,7 +6586,7 @@ var updateCommand = new Command13("update").description("\u68C0\u67E5\u5E76\u66F
5786
6586
  logger.info("Dry run \u6A21\u5F0F\uFF0C\u4E0D\u6267\u884C\u5B9E\u9645\u66F4\u65B0");
5787
6587
  return;
5788
6588
  }
5789
- const answers = await inquirer9.prompt([
6589
+ const answers = await inquirer10.prompt([
5790
6590
  {
5791
6591
  type: "confirm",
5792
6592
  name: "shouldUpdate",
@@ -5813,7 +6613,7 @@ async function performUpdate(projectPath, updates) {
5813
6613
  for (const update of updates) {
5814
6614
  const { type, info, updateOptions } = update;
5815
6615
  const targetDir = type === "frontend" ? "frontend" : "backend";
5816
- const targetPath = path18.join(projectPath, targetDir);
6616
+ const targetPath = path19.join(projectPath, targetDir);
5817
6617
  logger.newLine();
5818
6618
  logger.step(`\u66F4\u65B0 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
5819
6619
  if (updateOptions?.tag || updateOptions?.branch) {
@@ -5844,8 +6644,8 @@ async function performUpdate(projectPath, updates) {
5844
6644
  }
5845
6645
  }
5846
6646
  const ref = updateOptions?.tag || updateOptions?.branch || "HEAD";
5847
- const backupDir = path18.join(projectPath, `.backup-${Date.now()}`);
5848
- await fs4.copy(targetPath, path18.join(backupDir, targetDir));
6647
+ const backupDir = path19.join(projectPath, `.backup-${Date.now()}`);
6648
+ await fs4.copy(targetPath, path19.join(backupDir, targetDir));
5849
6649
  logger.info(`\u5DF2\u521B\u5EFA\u5907\u4EFD: ${backupDir}`);
5850
6650
  if (updateOptions?.dryRun) {
5851
6651
  logger.info("[Dry Run] \u5C06\u4F1A\u66F4\u65B0\u5230\u4EE5\u4E0B\u7248\u672C:");
@@ -5855,7 +6655,7 @@ async function performUpdate(projectPath, updates) {
5855
6655
  continue;
5856
6656
  }
5857
6657
  try {
5858
- const tempDir = path18.join(projectPath, `.template-update-${Date.now()}`);
6658
+ const tempDir = path19.join(projectPath, `.template-update-${Date.now()}`);
5859
6659
  await execa4("git", ["clone", "--depth=1", "--branch", ref, info.repository, tempDir], {
5860
6660
  stdio: "pipe"
5861
6661
  });
@@ -5872,7 +6672,7 @@ async function performUpdate(projectPath, updates) {
5872
6672
  const currentFiles = await FileUtils.findFiles("*", targetPath);
5873
6673
  for (const file of currentFiles) {
5874
6674
  if (!keepFiles.includes(file)) {
5875
- const filePath = path18.join(targetPath, file);
6675
+ const filePath = path19.join(targetPath, file);
5876
6676
  try {
5877
6677
  await fs4.remove(filePath);
5878
6678
  } catch {
@@ -5894,7 +6694,7 @@ async function performUpdate(projectPath, updates) {
5894
6694
  logger.error(`\u66F4\u65B0\u5931\u8D25: ${error.message}`);
5895
6695
  logger.info("\u6B63\u5728\u6062\u590D\u5907\u4EFD...");
5896
6696
  await fs4.remove(targetPath);
5897
- await fs4.copy(path18.join(backupDir, targetDir), targetPath);
6697
+ await fs4.copy(path19.join(backupDir, targetDir), targetPath);
5898
6698
  await fs4.remove(backupDir);
5899
6699
  logger.info("\u5DF2\u6062\u590D\u5230\u66F4\u65B0\u524D\u7684\u72B6\u6001");
5900
6700
  }
@@ -5915,16 +6715,16 @@ init_esm_shims();
5915
6715
  init_user_config();
5916
6716
  init_gitlab_api();
5917
6717
  init_logger();
5918
- import { Command as Command14 } from "commander";
5919
- import inquirer10 from "inquirer";
6718
+ import { Command as Command15 } from "commander";
6719
+ import inquirer11 from "inquirer";
5920
6720
  import chalk2 from "chalk";
5921
- var setTokenCommand = new Command14("set-token").description("\u8BBE\u7F6E GitLab Access Token").option("-t, --token <token>", "Access Token").option("-u, --url <url>", "GitLab Base URL", "https://gitlab.com").action(async (options) => {
6721
+ var setTokenCommand = new Command15("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) => {
5922
6722
  try {
5923
6723
  logger.header("GitLab Access Token \u914D\u7F6E");
5924
6724
  logger.newLine();
5925
6725
  let { token, url } = options;
5926
6726
  if (!token) {
5927
- const answers = await inquirer10.prompt([
6727
+ const answers = await inquirer11.prompt([
5928
6728
  {
5929
6729
  type: "password",
5930
6730
  name: "token",
@@ -5986,7 +6786,7 @@ var setTokenCommand = new Command14("set-token").description("\u8BBE\u7F6E GitLa
5986
6786
  process.exit(1);
5987
6787
  }
5988
6788
  });
5989
- var showConfigCommand = new Command14("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
6789
+ var showConfigCommand = new Command15("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
5990
6790
  try {
5991
6791
  logger.header("GitLab \u914D\u7F6E");
5992
6792
  logger.newLine();
@@ -6025,14 +6825,14 @@ var showConfigCommand = new Command14("show").description("\u663E\u793A\u5F53\u5
6025
6825
  process.exit(1);
6026
6826
  }
6027
6827
  });
6028
- var removeConfigCommand = new Command14("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
6828
+ var removeConfigCommand = new Command15("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
6029
6829
  try {
6030
6830
  const hasConfig = await userConfigManager.hasConfig();
6031
6831
  if (!hasConfig) {
6032
6832
  logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
6033
6833
  return;
6034
6834
  }
6035
- const answers = await inquirer10.prompt([
6835
+ const answers = await inquirer11.prompt([
6036
6836
  {
6037
6837
  type: "confirm",
6038
6838
  name: "confirm",
@@ -6054,7 +6854,7 @@ var removeConfigCommand = new Command14("remove").alias("rm").description("\u522
6054
6854
  process.exit(1);
6055
6855
  }
6056
6856
  });
6057
- var validateTokenCommand = new Command14("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
6857
+ var validateTokenCommand = new Command15("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
6058
6858
  try {
6059
6859
  logger.header("\u9A8C\u8BC1 GitLab Token");
6060
6860
  logger.newLine();
@@ -6084,17 +6884,17 @@ var validateTokenCommand = new Command14("validate").alias("test").description("
6084
6884
  process.exit(1);
6085
6885
  }
6086
6886
  });
6087
- var configCommand = new Command14("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
6887
+ var configCommand = new Command15("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
6088
6888
 
6089
6889
  // src/commands/diff.ts
6090
6890
  init_esm_shims();
6091
- import { Command as Command15 } from "commander";
6891
+ import { Command as Command16 } from "commander";
6092
6892
  import chalk3 from "chalk";
6093
6893
  init_logger();
6094
6894
  init_utils();
6095
6895
  init_user_config();
6096
6896
  init_gitlab_api();
6097
- var diffCommand = new Command15("diff").description("\u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02").option("-f, --frontend", "\u5BF9\u6BD4\u524D\u7AEF\u6A21\u677F").option("-b, --backend", "\u5BF9\u6BD4\u540E\u7AEF\u6A21\u677F").option("-t, --tag <tag>", "\u6307\u5B9A\u8FDC\u7A0B\u6807\u7B7E").option("-B, --branch <branch>", "\u6307\u5B9A\u8FDC\u7A0B\u5206\u652F").option("-o, --output <format>", "\u8F93\u51FA\u683C\u5F0F (table|json|diff)", "table").action(async (options) => {
6897
+ var diffCommand = new Command16("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) => {
6098
6898
  try {
6099
6899
  logger.header("\u6A21\u677F\u7248\u672C\u5BF9\u6BD4");
6100
6900
  logger.newLine();
@@ -6380,9 +7180,9 @@ var Table = class {
6380
7180
  };
6381
7181
 
6382
7182
  // src/index.ts
6383
- var __dirname2 = path19.dirname(fileURLToPath2(import.meta.url));
6384
- var pkg = fs5.readJsonSync(path19.join(__dirname2, "../package.json"));
6385
- var program = new Command16();
7183
+ var __dirname2 = path20.dirname(fileURLToPath2(import.meta.url));
7184
+ var pkg = fs5.readJsonSync(path20.join(__dirname2, "../package.json"));
7185
+ var program = new Command17();
6386
7186
  program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
6387
7187
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
6388
7188
  program.addCommand(initCommand);
@@ -6392,6 +7192,7 @@ program.addCommand(devCommand);
6392
7192
  program.addCommand(addFeatureCommand);
6393
7193
  program.addCommand(bugfixCommand);
6394
7194
  program.addCommand(hotfixCommand);
7195
+ program.addCommand(acceptCommand);
6395
7196
  program.addCommand(lintCommand);
6396
7197
  program.addCommand(statusCommand);
6397
7198
  program.addCommand(detectDepsCommand);
@@ -6413,7 +7214,8 @@ function showHelp() {
6413
7214
  console.log(" team-cli split-prd <prd-folder> \u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs");
6414
7215
  console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
6415
7216
  console.log(" team-cli dev \u5F00\u53D1\u6A21\u5F0F\uFF0C\u6267\u884C\u5177\u4F53\u4EFB\u52A1");
6416
- console.log(" team-cli add-feature <name> \u6DFB\u52A0\u65B0\u529F\u80FD");
7217
+ console.log(" team-cli accept [spec-file] \u9A8C\u6536\u529F\u80FD\uFF0C\u8D70\u67E5\u6240\u6709\u9700\u6C42");
7218
+ console.log(" team-cli add-feature <name> \u6DFB\u52A0\u65B0\u529F\u80FD");
6417
7219
  console.log(" team-cli bugfix \u521B\u5EFA Bugfix \u8BB0\u5F55");
6418
7220
  console.log(" team-cli hotfix \u521B\u5EFA Hotfix");
6419
7221
  console.log(" team-cli detect-deps [spec] \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
@@ -6438,6 +7240,7 @@ function showHelp() {
6438
7240
  console.log(" 1. PRD \u2192 specs (split-prd)");
6439
7241
  console.log(" 2. spec \u2192 milestones + todos (breakdown)");
6440
7242
  console.log(" 3. \u9009\u62E9 milestone/todo \u2192 \u5B9E\u73B0 (dev)");
7243
+ console.log(" 4. \u9A8C\u6536 (accept) \u2192 \u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210");
6441
7244
  console.log("");
6442
7245
  console.log(chalk4.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
6443
7246
  console.log(" team-cli add-feature <name> # \u6DFB\u52A0\u65B0\u529F\u80FD");